{"thread":{"id":"63357","subject":"[PATCH 0/9] t/unit-tests: convert unit-tests to use clar","startedAt":"2025-04-27T18:54:24Z","lastAt":"2025-04-29T15:44:19Z","messageCount":14,"participants":["Seyi Kuforiji","Junio C Hamano","Seyi Chamber","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"516855","messageId":"20250427185351.82520-1-kuforiji98@gmail.com","threadId":"63357","inReplyTo":null,"subject":"[PATCH 0/9] t/unit-tests: convert unit-tests to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:42Z","receivedAt":"2025-04-27T18:54:24Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Hello,\n\nThis small patch series marks the final batch of our existing unit test\nfiles transitioned to the Clar testing framework. It covers all the\nreftable-related test files, and is part of our ongoing effort to\nstandardize our testing framework to enhance its maintainability.\n\nThanks\nSeyi\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n\nSeyi Kuforiji (9):\n  t/unit-tests: adapt lib-reftable{c,h} helper functions to clar\n  t/unit-tests: convert reftable basics test to use clar test framework\n  t/unit-tests: convert reftable block test to use clar\n  t/unit-tests: convert reftable merged test to use clar\n  t/unit-tests: convert reftable pq test to use clar\n  t/unit-tests: convert reftable reader test to use clar\n  t/unit-tests: convert reftable readwrite test to use clar\n  t/unit-tests: convert reftable record test to use clar\n  t/unit-tests: convert reftable stack test to use clar\n\n Makefile                            |   20 +-\n t/meson.build                       |   21 +-\n t/unit-tests/lib-reftable.c         |   26 +-\n t/unit-tests/lib-reftable.h         |    6 +-\n t/unit-tests/t-reftable-basics.c    |  219 ----\n t/unit-tests/t-reftable-block.c     |  383 -------\n t/unit-tests/t-reftable-merged.c    |  546 ----------\n t/unit-tests/t-reftable-pq.c        |  161 ---\n t/unit-tests/t-reftable-reader.c    |   96 --\n t/unit-tests/t-reftable-readwrite.c |  985 ------------------\n t/unit-tests/t-reftable-record.c    |  585 -----------\n t/unit-tests/t-reftable-stack.c     | 1451 ---------------------------\n t/unit-tests/u-reftable-basics.c    |  195 ++++\n t/unit-tests/u-reftable-block.c     |  373 +++++++\n t/unit-tests/u-reftable-merged.c    |  515 ++++++++++\n t/unit-tests/u-reftable-pq.c        |  152 +++\n t/unit-tests/u-reftable-reader.c    |   78 ++\n t/unit-tests/u-reftable-readwrite.c |  870 ++++++++++++++++\n t/unit-tests/u-reftable-record.c    |  565 +++++++++++\n t/unit-tests/u-reftable-stack.c     | 1247 +++++++++++++++++++++++\n 20 files changed, 4031 insertions(+), 4463 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-basics.c\n delete mode 100644 t/unit-tests/t-reftable-block.c\n delete mode 100644 t/unit-tests/t-reftable-merged.c\n delete mode 100644 t/unit-tests/t-reftable-pq.c\n delete mode 100644 t/unit-tests/t-reftable-reader.c\n delete mode 100644 t/unit-tests/t-reftable-readwrite.c\n delete mode 100644 t/unit-tests/t-reftable-record.c\n delete mode 100644 t/unit-tests/t-reftable-stack.c\n create mode 100644 t/unit-tests/u-reftable-basics.c\n create mode 100644 t/unit-tests/u-reftable-block.c\n create mode 100644 t/unit-tests/u-reftable-merged.c\n create mode 100644 t/unit-tests/u-reftable-pq.c\n create mode 100644 t/unit-tests/u-reftable-reader.c\n create mode 100644 t/unit-tests/u-reftable-readwrite.c\n create mode 100644 t/unit-tests/u-reftable-record.c\n create mode 100644 t/unit-tests/u-reftable-stack.c\n\n-- \n2.43.0\n\n"},{"id":"516856","messageId":"20250427185351.82520-2-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 1/9] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:43Z","receivedAt":"2025-04-27T18:54:32Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are\nrequired for the reftable-related test files to run efficeintly. In the\ncurrent implementation these functions are designed to conform with our\nhomegrown unit-testing structure. So in other to convert the reftable\ntest files, there is need for a clar specific implementation of these\nhelper functions.\n\ntype cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++)`\nAdapt functions in lib-reftable.{c,h} to use clar. These functions\nconform with the clar testing framework and become available for all\nreftable-related test files implemented using the clar testing\nframework, which requires them. This will be used by subsequent commits.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                    |  4 ++--\n t/meson.build               |  4 ++--\n t/unit-tests/lib-reftable.c | 26 +++++++++++++-------------\n t/unit-tests/lib-reftable.h |  6 +++---\n 4 files changed, 20 insertions(+), 20 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 13f9062a05..1a0f8c30cd 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1371,8 +1371,9 @@ CLAR_TEST_SUITES += u-urlmatch-normalization\n CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)\n CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES))\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o\n-CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n+CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n+CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n UNIT_TEST_PROGRAMS += t-reftable-basics\n UNIT_TEST_PROGRAMS += t-reftable-block\n@@ -1384,7 +1385,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record\n UNIT_TEST_PROGRAMS += t-reftable-stack\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n-UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n \n # xdiff and reftable libs may in turn depend on what is in libgit.a\n GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)\ndiff --git a/t/meson.build b/t/meson.build\nindex bfb744e886..0102894b6f 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -18,8 +18,9 @@ clar_test_suites = [\n \n clar_sources = [\n   'unit-tests/clar/clar.c',\n+  'unit-tests/lib-oid.c',\n+  'unit-tests/lib-reftable.c',\n   'unit-tests/unit-test.c',\n-  'unit-tests/lib-oid.c'\n ]\n \n clar_decls_h = custom_target(\n@@ -69,7 +70,6 @@ foreach unit_test_program : unit_test_programs\n   unit_test = executable(unit_test_name,\n     sources: [\n       'unit-tests/test-lib.c',\n-      'unit-tests/lib-reftable.c',\n       unit_test_program,\n     ],\n     dependencies: [libgit_commonmain],\ndiff --git a/t/unit-tests/lib-reftable.c b/t/unit-tests/lib-reftable.c\nindex 8a69612266..414364166f 100644\n--- a/t/unit-tests/lib-reftable.c\n+++ b/t/unit-tests/lib-reftable.c\n@@ -1,12 +1,12 @@\n #define DISABLE_SIGN_COMPARE_WARNINGS\n \n #include \"lib-reftable.h\"\n-#include \"test-lib.h\"\n+#include \"unit-test.h\"\n #include \"reftable/constants.h\"\n #include \"reftable/writer.h\"\n #include \"strbuf.h\"\n \n-void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)\n+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)\n {\n \tmemset(p, (uint8_t)i, hash_size(id));\n }\n@@ -22,17 +22,17 @@ static int strbuf_writer_flush(void *arg UNUSED)\n \treturn 0;\n }\n \n-struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,\n+struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,\n \t\t\t\t\t\t struct reftable_write_options *opts)\n {\n \tstruct reftable_writer *writer;\n \tint ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,\n \t\t\t\t      buf, opts);\n-\tcheck(!ret);\n+\tcl_assert(ret == 0);\n \treturn writer;\n }\n \n-void t_reftable_write_to_buf(struct reftable_buf *buf,\n+void cl_reftable_write_to_buf(struct reftable_buf *buf,\n \t\t\t     struct reftable_ref_record *refs,\n \t\t\t     size_t nrefs,\n \t\t\t     struct reftable_log_record *logs,\n@@ -64,35 +64,35 @@ void t_reftable_write_to_buf(struct reftable_buf *buf,\n \t\t\tmin = ui;\n \t}\n \n-\twriter = t_reftable_strbuf_writer(buf, &opts);\n+\twriter = cl_reftable_strbuf_writer(buf, &opts);\n \treftable_writer_set_limits(writer, min, max);\n \n \tif (nrefs) {\n \t\tret = reftable_writer_add_refs(writer, refs, nrefs);\n-\t\tcheck_int(ret, ==, 0);\n+\t\tcl_assert_equal_i(ret, 0);\n \t}\n \n \tif (nlogs) {\n \t\tret = reftable_writer_add_logs(writer, logs, nlogs);\n-\t\tcheck_int(ret, ==, 0);\n+\t\tcl_assert_equal_i(ret, 0);\n \t}\n \n \tret = reftable_writer_close(writer);\n-\tcheck_int(ret, ==, 0);\n+\tcl_assert_equal_i(ret, 0);\n \n \tstats = reftable_writer_stats(writer);\n-\tfor (size_t i = 0; i < stats->ref_stats.blocks; i++) {\n+\tfor (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++) {\n \t\tsize_t off = i * (opts.block_size ? opts.block_size\n \t\t\t\t\t\t  : DEFAULT_BLOCK_SIZE);\n \t\tif (!off)\n \t\t\toff = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);\n-\t\tcheck_char(buf->buf[off], ==, 'r');\n+\t\tcl_assert(buf->buf[off] == 'r');\n \t}\n \n \tif (nrefs)\n-\t\tcheck_int(stats->ref_stats.blocks, >, 0);\n+\t\tcl_assert(stats->ref_stats.blocks > 0);\n \tif (nlogs)\n-\t\tcheck_int(stats->log_stats.blocks, >, 0);\n+\t\tcl_assert(stats->log_stats.blocks > 0);\n \n \treftable_writer_free(writer);\n }\ndiff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\nindex e4c360fa7e..2958db5dc0 100644\n--- a/t/unit-tests/lib-reftable.h\n+++ b/t/unit-tests/lib-reftable.h\n@@ -6,12 +6,12 @@\n \n struct reftable_buf;\n \n-void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);\n+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);\n \n-struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,\n+struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,\n \t\t\t\t\t\t struct reftable_write_options *opts);\n \n-void t_reftable_write_to_buf(struct reftable_buf *buf,\n+void cl_reftable_write_to_buf(struct reftable_buf *buf,\n \t\t\t     struct reftable_ref_record *refs,\n \t\t\t     size_t nrecords,\n \t\t\t     struct reftable_log_record *logs,\n-- \n2.43.0\n\n"},{"id":"516857","messageId":"20250427185351.82520-3-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 2/9] t/unit-tests: convert reftable basics test to use clar test framework","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:44Z","receivedAt":"2025-04-27T18:54:37Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable basics test file to clar by using clar assertions\nwhere necessary.Break up test edge case to improve modularity and\nclarity.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                         |   2 +-\n t/meson.build                    |   2 +-\n t/unit-tests/t-reftable-basics.c | 219 -------------------------------\n t/unit-tests/u-reftable-basics.c | 195 +++++++++++++++++++++++++++\n 4 files changed, 197 insertions(+), 221 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-basics.c\n create mode 100644 t/unit-tests/u-reftable-basics.c\n\ndiff --git a/Makefile b/Makefile\nindex 1a0f8c30cd..07e37bd1f3 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1362,6 +1362,7 @@ CLAR_TEST_SUITES += u-oid-array\n CLAR_TEST_SUITES += u-oidmap\n CLAR_TEST_SUITES += u-oidtree\n CLAR_TEST_SUITES += u-prio-queue\n+CLAR_TEST_SUITES += u-reftable-basics\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1375,7 +1376,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-basics\n UNIT_TEST_PROGRAMS += t-reftable-block\n UNIT_TEST_PROGRAMS += t-reftable-merged\n UNIT_TEST_PROGRAMS += t-reftable-pq\ndiff --git a/t/meson.build b/t/meson.build\nindex 0102894b6f..9add1b3d8b 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -8,6 +8,7 @@ clar_test_suites = [\n   'unit-tests/u-oidmap.c',\n   'unit-tests/u-oidtree.c',\n   'unit-tests/u-prio-queue.c',\n+  'unit-tests/u-reftable-basics.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -55,7 +56,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-basics.c',\n   'unit-tests/t-reftable-block.c',\n   'unit-tests/t-reftable-merged.c',\n   'unit-tests/t-reftable-pq.c',\ndiff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c\ndeleted file mode 100644\nindex c9e751e49e..0000000000\n--- a/t/unit-tests/t-reftable-basics.c\n+++ /dev/null\n@@ -1,219 +0,0 @@\n-/*\n-Copyright 2020 Google LLC\n-\n-Use of this source code is governed by a BSD-style\n-license that can be found in the LICENSE file or at\n-https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#include \"test-lib.h\"\n-#include \"reftable/basics.h\"\n-\n-struct integer_needle_lesseq_args {\n-\tint needle;\n-\tint *haystack;\n-};\n-\n-static int integer_needle_lesseq(size_t i, void *_args)\n-{\n-\tstruct integer_needle_lesseq_args *args = _args;\n-\treturn args->needle <= args->haystack[i];\n-}\n-\n-static void *realloc_stub(void *p UNUSED, size_t size UNUSED)\n-{\n-\treturn NULL;\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tif_test (\"binary search with binsearch works\") {\n-\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n-\t\tstruct {\n-\t\t\tint needle;\n-\t\t\tsize_t expected_idx;\n-\t\t} testcases[] = {\n-\t\t\t{-9000, 0},\n-\t\t\t{-1, 0},\n-\t\t\t{0, 0},\n-\t\t\t{2, 0},\n-\t\t\t{3, 1},\n-\t\t\t{4, 1},\n-\t\t\t{7, 3},\n-\t\t\t{9, 4},\n-\t\t\t{10, 4},\n-\t\t\t{11, 5},\n-\t\t\t{9000, 5},\n-\t\t};\n-\n-\t\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n-\t\t\tstruct integer_needle_lesseq_args args = {\n-\t\t\t\t.haystack = haystack,\n-\t\t\t\t.needle = testcases[i].needle,\n-\t\t\t};\n-\t\t\tsize_t idx;\n-\n-\t\t\tidx = binsearch(ARRAY_SIZE(haystack),\n-\t\t\t\t\t&integer_needle_lesseq, &args);\n-\t\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n-\t\t}\n-\t}\n-\n-\tif_test (\"names_length returns size of a NULL-terminated string array\") {\n-\t\tconst char *a[] = { \"a\", \"b\", NULL };\n-\t\tcheck_int(names_length(a), ==, 2);\n-\t}\n-\n-\tif_test (\"names_equal compares NULL-terminated string arrays\") {\n-\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n-\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n-\t\tconst char *c[] = { \"a\", \"b\", NULL };\n-\n-\t\tcheck(names_equal(a, a));\n-\t\tcheck(!names_equal(a, b));\n-\t\tcheck(!names_equal(a, c));\n-\t}\n-\n-\tif_test (\"parse_names works for basic input\") {\n-\t\tchar in1[] = \"line\\n\";\n-\t\tchar in2[] = \"a\\nb\\nc\";\n-\t\tchar **out = parse_names(in1, strlen(in1));\n-\t\tcheck(out != NULL);\n-\t\tcheck_str(out[0], \"line\");\n-\t\tcheck(!out[1]);\n-\t\tfree_names(out);\n-\n-\t\tout = parse_names(in2, strlen(in2));\n-\t\tcheck(out != NULL);\n-\t\tcheck_str(out[0], \"a\");\n-\t\tcheck_str(out[1], \"b\");\n-\t\tcheck_str(out[2], \"c\");\n-\t\tcheck(!out[3]);\n-\t\tfree_names(out);\n-\t}\n-\n-\tif_test (\"parse_names drops empty string\") {\n-\t\tchar in[] = \"a\\n\\nb\\n\";\n-\t\tchar **out = parse_names(in, strlen(in));\n-\t\tcheck(out != NULL);\n-\t\tcheck_str(out[0], \"a\");\n-\t\t/* simply '\\n' should be dropped as empty string */\n-\t\tcheck_str(out[1], \"b\");\n-\t\tcheck(!out[2]);\n-\t\tfree_names(out);\n-\t}\n-\n-\tif_test (\"common_prefix_size works\") {\n-\t\tstruct reftable_buf a = REFTABLE_BUF_INIT;\n-\t\tstruct reftable_buf b = REFTABLE_BUF_INIT;\n-\t\tstruct {\n-\t\t\tconst char *a, *b;\n-\t\t\tint want;\n-\t\t} cases[] = {\n-\t\t\t{\"abcdef\", \"abc\", 3},\n-\t\t\t{ \"abc\", \"ab\", 2 },\n-\t\t\t{ \"\", \"abc\", 0 },\n-\t\t\t{ \"abc\", \"abd\", 2 },\n-\t\t\t{ \"abc\", \"pqr\", 0 },\n-\t\t};\n-\n-\t\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n-\t\t\tcheck(!reftable_buf_addstr(&a, cases[i].a));\n-\t\t\tcheck(!reftable_buf_addstr(&b, cases[i].b));\n-\t\t\tcheck_uint(common_prefix_size(&a, &b), ==, cases[i].want);\n-\t\t\treftable_buf_reset(&a);\n-\t\t\treftable_buf_reset(&b);\n-\t\t}\n-\t\treftable_buf_release(&a);\n-\t\treftable_buf_release(&b);\n-\t}\n-\n-\tif_test (\"reftable_put_be64 and reftable_get_be64 work\") {\n-\t\tuint64_t in = 0x1122334455667788;\n-\t\tuint8_t dest[8];\n-\t\tuint64_t out;\n-\t\treftable_put_be64(dest, in);\n-\t\tout = reftable_get_be64(dest);\n-\t\tcheck_int(in, ==, out);\n-\t}\n-\n-\tif_test (\"reftable_put_be32 and reftable_get_be32 work\") {\n-\t\tuint32_t in = 0x11223344;\n-\t\tuint8_t dest[4];\n-\t\tuint32_t out;\n-\t\treftable_put_be32(dest, in);\n-\t\tout = reftable_get_be32(dest);\n-\t\tcheck_int(in, ==, out);\n-\t}\n-\n-\tif_test (\"reftable_put_be24 and reftable_get_be24 work\") {\n-\t\tuint32_t in = 0x112233;\n-\t\tuint8_t dest[3];\n-\t\tuint32_t out;\n-\t\treftable_put_be24(dest, in);\n-\t\tout = reftable_get_be24(dest);\n-\t\tcheck_int(in, ==, out);\n-\t}\n-\n-\tif_test (\"put_be16 and get_be16 work\") {\n-\t\tuint32_t in = 0xfef1;\n-\t\tuint8_t dest[3];\n-\t\tuint32_t out;\n-\t\treftable_put_be16(dest, in);\n-\t\tout = reftable_get_be16(dest);\n-\t\tcheck_int(in, ==, out);\n-\t}\n-\n-\tif_test (\"REFTABLE_ALLOC_GROW works\") {\n-\t\tint *arr = NULL, *old_arr;\n-\t\tsize_t alloc = 0, old_alloc;\n-\n-\t\tcheck(!REFTABLE_ALLOC_GROW(arr, 1, alloc));\n-\t\tcheck(arr != NULL);\n-\t\tcheck_uint(alloc, >=, 1);\n-\t\tarr[0] = 42;\n-\n-\t\told_alloc = alloc;\n-\t\told_arr = arr;\n-\t\treftable_set_alloc(NULL, realloc_stub, NULL);\n-\t\tcheck(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));\n-\t\tcheck(arr == old_arr);\n-\t\tcheck_uint(alloc, ==, old_alloc);\n-\n-\t\told_alloc = alloc;\n-\t\treftable_set_alloc(NULL, NULL, NULL);\n-\t\tcheck(!REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));\n-\t\tcheck(arr != NULL);\n-\t\tcheck_uint(alloc, >, old_alloc);\n-\t\tarr[alloc - 1] = 42;\n-\n-\t\treftable_free(arr);\n-\t}\n-\n-\tif_test (\"REFTABLE_ALLOC_GROW_OR_NULL works\") {\n-\t\tint *arr = NULL;\n-\t\tsize_t alloc = 0, old_alloc;\n-\n-\t\tREFTABLE_ALLOC_GROW_OR_NULL(arr, 1, alloc);\n-\t\tcheck(arr != NULL);\n-\t\tcheck_uint(alloc, >=, 1);\n-\t\tarr[0] = 42;\n-\n-\t\told_alloc = alloc;\n-\t\tREFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);\n-\t\tcheck(arr != NULL);\n-\t\tcheck_uint(alloc, >, old_alloc);\n-\t\tarr[alloc - 1] = 42;\n-\n-\t\told_alloc = alloc;\n-\t\treftable_set_alloc(NULL, realloc_stub, NULL);\n-\t\tREFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);\n-\t\tcheck(arr == NULL);\n-\t\tcheck_uint(alloc, ==, 0);\n-\t\treftable_set_alloc(NULL, NULL, NULL);\n-\n-\t\treftable_free(arr);\n-\t}\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-basics.c b/t/unit-tests/u-reftable-basics.c\nnew file mode 100644\nindex 0000000000..63dd568faf\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-basics.c\n@@ -0,0 +1,195 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"unit-test.h\"\n+#include \"reftable/basics.h\"\n+\n+struct integer_needle_lesseq_args {\n+\tint needle;\n+\tint *haystack;\n+};\n+\n+static int integer_needle_lesseq(size_t i, void *_args)\n+{\n+\tstruct integer_needle_lesseq_args *args = _args;\n+\treturn args->needle <= args->haystack[i];\n+}\n+\n+static void *realloc_stub(void *p UNUSED, size_t size UNUSED)\n+{\n+\treturn NULL;\n+}\n+\n+void test_reftable_basics__binsearch(void)\n+{\n+\tint haystack[] = { 2, 4, 6, 8, 10 };\n+\tstruct {\n+\t\tint needle;\n+\t\tsize_t expected_idx;\n+\t} testcases[] = {\n+\t\t{-9000, 0},\n+\t\t{-1, 0},\n+\t\t{0, 0},\n+\t\t{2, 0},\n+\t\t{3, 1},\n+\t\t{4, 1},\n+\t\t{7, 3},\n+\t\t{9, 4},\n+\t\t{10, 4},\n+\t\t{11, 5},\n+\t\t{9000, 5},\n+\t};\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n+\t\tstruct integer_needle_lesseq_args args = {\n+\t\t\t.haystack = haystack,\n+\t\t\t.needle = testcases[i].needle,\n+\t\t};\n+\t\tsize_t idx;\n+\n+\t\tidx = binsearch(ARRAY_SIZE(haystack),\n+\t\t\t\t&integer_needle_lesseq, &args);\n+\t\tcl_assert_equal_i(idx, testcases[i].expected_idx);\n+\t}\n+\n+}\n+\n+void test_reftable_basics__names_length(void)\n+{\n+\tconst char *a[] = { \"a\", \"b\", NULL };\n+\tcl_assert_equal_i(names_length(a), 2);\n+}\n+\n+void test_reftable_basics__names_equal(void)\n+{\n+\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\tconst char *c[] = { \"a\", \"b\", NULL };\n+\n+\tcl_assert(names_equal(a, a));\n+\tcl_assert(!names_equal(a, b));\n+\tcl_assert(!names_equal(a, c));\n+}\n+\n+void test_reftable_basics__parse_names(void)\n+{\n+\tchar in1[] = \"line\\n\";\n+\tchar in2[] = \"a\\nb\\nc\";\n+\tchar **out = parse_names(in1, strlen(in1));\n+\tcl_assert(out != NULL);\n+\tcl_assert_equal_s(out[0], \"line\");\n+\tcl_assert(!out[1]);\n+\tfree_names(out);\n+\n+\tout = parse_names(in2, strlen(in2));\n+\tcl_assert(out != NULL);\n+\tcl_assert_equal_s(out[0], \"a\");\n+\tcl_assert_equal_s(out[1], \"b\");\n+\tcl_assert_equal_s(out[2], \"c\");\n+\tcl_assert(!out[3]);\n+\tfree_names(out);\n+}\n+\n+void test_reftable_basics__common_prefix_size(void)\n+{\n+\tstruct reftable_buf a = REFTABLE_BUF_INIT;\n+\tstruct reftable_buf b = REFTABLE_BUF_INIT;\n+\tstruct {\n+\t\tconst char *a, *b;\n+\t\tint want;\n+\t} cases[] = {\n+\t\t{\"abcdef\", \"abc\", 3},\n+\t\t{ \"abc\", \"ab\", 2 },\n+\t\t{ \"\", \"abc\", 0 },\n+\t\t{ \"abc\", \"abd\", 2 },\n+\t\t{ \"abc\", \"pqr\", 0 },\n+\t};\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\treftable_buf_reset(&a);\n+\t\treftable_buf_reset(&b);\n+\t\tcl_assert_equal_i(reftable_buf_addstr(&a, cases[i].a), 0);\n+\t\tcl_assert_equal_i(reftable_buf_addstr(&b, cases[i].b), 0);\n+\t\tcl_assert_equal_i(common_prefix_size(&a, &b), cases[i].want);\n+\t}\n+\treftable_buf_release(&a);\n+\treftable_buf_release(&b);\n+}\n+\n+void test_reftable_basics__put_get_be24(void)\n+{\n+\tuint32_t in = 0x112233;\n+\tuint8_t dest[3];\n+\tuint32_t out;\n+\treftable_put_be24(dest, in);\n+\tout = reftable_get_be24(dest);\n+\tcl_assert_equal_i(in, out);\n+}\n+\n+void test_reftable_basics__put_get_be16(void)\n+{\n+\tuint32_t in = 0xfef1;\n+\tuint8_t dest[3];\n+\tuint32_t out;\n+\treftable_put_be16(dest, in);\n+\tout = reftable_get_be16(dest);\n+\tcl_assert_equal_i(in, out);\n+}\n+\n+void test_reftable_basics__grow_alloc(void)\n+{\n+\tint *arr = NULL, *old_arr;\n+\tsize_t alloc = 0, old_alloc;\n+\n+\tcl_assert_equal_i(REFTABLE_ALLOC_GROW(arr, 1, alloc), 0);\n+\tcl_assert(arr != NULL);\n+\tcl_assert(alloc >= 1);\n+\tarr[0] = 42;\n+\n+\told_alloc = alloc;\n+\told_arr = arr;\n+\treftable_set_alloc(NULL, realloc_stub, NULL);\n+\tcl_assert(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));\n+\tcl_assert(arr == old_arr);\n+\tcl_assert_equal_i(alloc, old_alloc);\n+\n+\told_alloc = alloc;\n+\treftable_set_alloc(NULL, NULL, NULL);\n+\tcl_assert_equal_i(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc), 0);\n+\tcl_assert(arr != NULL);\n+\tcl_assert(alloc > old_alloc);\n+\tarr[alloc - 1] = 42;\n+\n+\treftable_free(arr);\n+}\n+\n+void test_reftable_basics__grow_alloc_or_null(void)\n+{\n+\tint *arr = NULL;\n+\tsize_t alloc = 0, old_alloc;\n+\n+\tREFTABLE_ALLOC_GROW_OR_NULL(arr, 1, alloc);\n+\tcl_assert(arr != NULL);\n+\tcl_assert(alloc >= 1);\n+\tarr[0] = 42;\n+\n+\told_alloc = alloc;\n+\tREFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);\n+\tcl_assert(arr != NULL);\n+\tcl_assert(alloc > old_alloc);\n+\tarr[alloc - 1] = 42;\n+\n+\told_alloc = alloc;\n+\treftable_set_alloc(NULL, realloc_stub, NULL);\n+\tREFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);\n+\tcl_assert(arr == NULL);\n+\tcl_assert_equal_i(alloc, 0);\n+\treftable_set_alloc(NULL, NULL, NULL);\n+\n+\treftable_free(arr);\n+}\n-- \n2.43.0\n\n"},{"id":"516858","messageId":"20250427185351.82520-4-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 3/9] t/unit-tests: convert reftable block test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:45Z","receivedAt":"2025-04-27T18:54:42Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable block test file to use clar testing framework by using\nclar assertions where necessary.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                        |   2 +-\n t/meson.build                   |   2 +-\n t/unit-tests/t-reftable-block.c | 383 --------------------------------\n t/unit-tests/u-reftable-block.c | 373 +++++++++++++++++++++++++++++++\n 4 files changed, 375 insertions(+), 385 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-block.c\n create mode 100644 t/unit-tests/u-reftable-block.c\n\ndiff --git a/Makefile b/Makefile\nindex 07e37bd1f3..3fc012f86a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1363,6 +1363,7 @@ CLAR_TEST_SUITES += u-oidmap\n CLAR_TEST_SUITES += u-oidtree\n CLAR_TEST_SUITES += u-prio-queue\n CLAR_TEST_SUITES += u-reftable-basics\n+CLAR_TEST_SUITES += u-reftable-block\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1376,7 +1377,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-block\n UNIT_TEST_PROGRAMS += t-reftable-merged\n UNIT_TEST_PROGRAMS += t-reftable-pq\n UNIT_TEST_PROGRAMS += t-reftable-reader\ndiff --git a/t/meson.build b/t/meson.build\nindex 9add1b3d8b..eb9e69eb27 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -9,6 +9,7 @@ clar_test_suites = [\n   'unit-tests/u-oidtree.c',\n   'unit-tests/u-prio-queue.c',\n   'unit-tests/u-reftable-basics.c',\n+  'unit-tests/u-reftable-block.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -56,7 +57,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-block.c',\n   'unit-tests/t-reftable-merged.c',\n   'unit-tests/t-reftable-pq.c',\n   'unit-tests/t-reftable-reader.c',\ndiff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c\ndeleted file mode 100644\nindex 22040aeefa..0000000000\n--- a/t/unit-tests/t-reftable-block.c\n+++ /dev/null\n@@ -1,383 +0,0 @@\n-/*\n-Copyright 2020 Google LLC\n-\n-Use of this source code is governed by a BSD-style\n-license that can be found in the LICENSE file or at\n-https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#include \"test-lib.h\"\n-#include \"reftable/block.h\"\n-#include \"reftable/blocksource.h\"\n-#include \"reftable/constants.h\"\n-#include \"reftable/reftable-error.h\"\n-#include \"strbuf.h\"\n-\n-static void t_ref_block_read_write(void)\n-{\n-\tconst int header_off = 21; /* random */\n-\tstruct reftable_record recs[30];\n-\tconst size_t N = ARRAY_SIZE(recs);\n-\tconst size_t block_size = 1024;\n-\tstruct reftable_block block = { 0 };\n-\tstruct block_writer bw = {\n-\t\t.last_key = REFTABLE_BUF_INIT,\n-\t};\n-\tstruct reftable_record rec = {\n-\t\t.type = BLOCK_TYPE_REF,\n-\t};\n-\tsize_t i = 0;\n-\tint ret;\n-\tstruct block_reader br = { 0 };\n-\tstruct block_iter it = BLOCK_ITER_INIT;\n-\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n-\n-\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n-\tcheck(block.data != NULL);\n-\tblock.len = block_size;\n-\tblock_source_from_buf(&block.source ,&buf);\n-\tret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n-\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n-\tcheck(!ret);\n-\n-\trec.u.ref.refname = (char *) \"\";\n-\trec.u.ref.value_type = REFTABLE_REF_DELETION;\n-\tret = block_writer_add(&bw, &rec);\n-\tcheck_int(ret, ==, REFTABLE_API_ERROR);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\trec.u.ref.refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n-\t\trec.u.ref.value_type = REFTABLE_REF_VAL1;\n-\t\tmemset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);\n-\n-\t\trecs[i] = rec;\n-\t\tret = block_writer_add(&bw, &rec);\n-\t\trec.u.ref.refname = NULL;\n-\t\trec.u.ref.value_type = REFTABLE_REF_DELETION;\n-\t\tcheck_int(ret, ==, 0);\n-\t}\n-\n-\tret = block_writer_finish(&bw);\n-\tcheck_int(ret, >, 0);\n-\n-\tblock_writer_release(&bw);\n-\n-\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n-\n-\tblock_iter_seek_start(&it, &br);\n-\n-\tfor (i = 0; ; i++) {\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, >=, 0);\n-\t\tif (ret > 0) {\n-\t\t\tcheck_int(i, ==, N);\n-\t\t\tbreak;\n-\t\t}\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tblock_iter_reset(&it);\n-\t\treftable_record_key(&recs[i], &want);\n-\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\n-\t\twant.len--;\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\t\tcheck(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tblock_reader_release(&br);\n-\tblock_iter_close(&it);\n-\treftable_record_release(&rec);\n-\treftable_block_done(&br.block);\n-\treftable_buf_release(&want);\n-\treftable_buf_release(&buf);\n-\tfor (i = 0; i < N; i++)\n-\t\treftable_record_release(&recs[i]);\n-}\n-\n-static void t_log_block_read_write(void)\n-{\n-\tconst int header_off = 21;\n-\tstruct reftable_record recs[30];\n-\tconst size_t N = ARRAY_SIZE(recs);\n-\tconst size_t block_size = 2048;\n-\tstruct reftable_block block = { 0 };\n-\tstruct block_writer bw = {\n-\t\t.last_key = REFTABLE_BUF_INIT,\n-\t};\n-\tstruct reftable_record rec = {\n-\t\t.type = BLOCK_TYPE_LOG,\n-\t};\n-\tsize_t i = 0;\n-\tint ret;\n-\tstruct block_reader br = { 0 };\n-\tstruct block_iter it = BLOCK_ITER_INIT;\n-\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n-\n-\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n-\tcheck(block.data != NULL);\n-\tblock.len = block_size;\n-\tblock_source_from_buf(&block.source ,&buf);\n-\tret = block_writer_init(&bw, BLOCK_TYPE_LOG, block.data, block_size,\n-\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n-\tcheck(!ret);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\trec.u.log.refname = xstrfmt(\"branch%02\"PRIuMAX , (uintmax_t)i);\n-\t\trec.u.log.update_index = i;\n-\t\trec.u.log.value_type = REFTABLE_LOG_UPDATE;\n-\n-\t\trecs[i] = rec;\n-\t\tret = block_writer_add(&bw, &rec);\n-\t\trec.u.log.refname = NULL;\n-\t\trec.u.log.value_type = REFTABLE_LOG_DELETION;\n-\t\tcheck_int(ret, ==, 0);\n-\t}\n-\n-\tret = block_writer_finish(&bw);\n-\tcheck_int(ret, >, 0);\n-\n-\tblock_writer_release(&bw);\n-\n-\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n-\n-\tblock_iter_seek_start(&it, &br);\n-\n-\tfor (i = 0; ; i++) {\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, >=, 0);\n-\t\tif (ret > 0) {\n-\t\t\tcheck_int(i, ==, N);\n-\t\t\tbreak;\n-\t\t}\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tblock_iter_reset(&it);\n-\t\treftable_buf_reset(&want);\n-\t\tcheck(!reftable_buf_addstr(&want, recs[i].u.log.refname));\n-\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\n-\t\twant.len--;\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\t\tcheck(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tblock_reader_release(&br);\n-\tblock_iter_close(&it);\n-\treftable_record_release(&rec);\n-\treftable_block_done(&br.block);\n-\treftable_buf_release(&want);\n-\treftable_buf_release(&buf);\n-\tfor (i = 0; i < N; i++)\n-\t\treftable_record_release(&recs[i]);\n-}\n-\n-static void t_obj_block_read_write(void)\n-{\n-\tconst int header_off = 21;\n-\tstruct reftable_record recs[30];\n-\tconst size_t N = ARRAY_SIZE(recs);\n-\tconst size_t block_size = 1024;\n-\tstruct reftable_block block = { 0 };\n-\tstruct block_writer bw = {\n-\t\t.last_key = REFTABLE_BUF_INIT,\n-\t};\n-\tstruct reftable_record rec = {\n-\t\t.type = BLOCK_TYPE_OBJ,\n-\t};\n-\tsize_t i = 0;\n-\tint ret;\n-\tstruct block_reader br = { 0 };\n-\tstruct block_iter it = BLOCK_ITER_INIT;\n-\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n-\n-\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n-\tcheck(block.data != NULL);\n-\tblock.len = block_size;\n-\tblock_source_from_buf(&block.source, &buf);\n-\tret = block_writer_init(&bw, BLOCK_TYPE_OBJ, block.data, block_size,\n-\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n-\tcheck(!ret);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tuint8_t bytes[] = { i, i + 1, i + 2, i + 3, i + 5 }, *allocated;\n-\t\tDUP_ARRAY(allocated, bytes, ARRAY_SIZE(bytes));\n-\n-\t\trec.u.obj.hash_prefix = allocated;\n-\t\trec.u.obj.hash_prefix_len = 5;\n-\n-\t\trecs[i] = rec;\n-\t\tret = block_writer_add(&bw, &rec);\n-\t\trec.u.obj.hash_prefix = NULL;\n-\t\trec.u.obj.hash_prefix_len = 0;\n-\t\tcheck_int(ret, ==, 0);\n-\t}\n-\n-\tret = block_writer_finish(&bw);\n-\tcheck_int(ret, >, 0);\n-\n-\tblock_writer_release(&bw);\n-\n-\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n-\n-\tblock_iter_seek_start(&it, &br);\n-\n-\tfor (i = 0; ; i++) {\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, >=, 0);\n-\t\tif (ret > 0) {\n-\t\t\tcheck_int(i, ==, N);\n-\t\t\tbreak;\n-\t\t}\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tblock_iter_reset(&it);\n-\t\treftable_record_key(&recs[i], &want);\n-\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tblock_reader_release(&br);\n-\tblock_iter_close(&it);\n-\treftable_record_release(&rec);\n-\treftable_block_done(&br.block);\n-\treftable_buf_release(&want);\n-\treftable_buf_release(&buf);\n-\tfor (i = 0; i < N; i++)\n-\t\treftable_record_release(&recs[i]);\n-}\n-\n-static void t_index_block_read_write(void)\n-{\n-\tconst int header_off = 21;\n-\tstruct reftable_record recs[30];\n-\tconst size_t N = ARRAY_SIZE(recs);\n-\tconst size_t block_size = 1024;\n-\tstruct reftable_block block = { 0 };\n-\tstruct block_writer bw = {\n-\t\t.last_key = REFTABLE_BUF_INIT,\n-\t};\n-\tstruct reftable_record rec = {\n-\t\t.type = BLOCK_TYPE_INDEX,\n-\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n-\t};\n-\tsize_t i = 0;\n-\tint ret;\n-\tstruct block_reader br = { 0 };\n-\tstruct block_iter it = BLOCK_ITER_INIT;\n-\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n-\n-\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n-\tcheck(block.data != NULL);\n-\tblock.len = block_size;\n-\tblock_source_from_buf(&block.source, &buf);\n-\tret = block_writer_init(&bw, BLOCK_TYPE_INDEX, block.data, block_size,\n-\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n-\tcheck(!ret);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tchar buf[128];\n-\n-\t\tsnprintf(buf, sizeof(buf), \"branch%02\"PRIuMAX, (uintmax_t)i);\n-\n-\t\treftable_buf_init(&recs[i].u.idx.last_key);\n-\t\trecs[i].type = BLOCK_TYPE_INDEX;\n-\t\tcheck(!reftable_buf_addstr(&recs[i].u.idx.last_key, buf));\n-\t\trecs[i].u.idx.offset = i;\n-\n-\t\tret = block_writer_add(&bw, &recs[i]);\n-\t\tcheck_int(ret, ==, 0);\n-\t}\n-\n-\tret = block_writer_finish(&bw);\n-\tcheck_int(ret, >, 0);\n-\n-\tblock_writer_release(&bw);\n-\n-\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n-\n-\tblock_iter_seek_start(&it, &br);\n-\n-\tfor (i = 0; ; i++) {\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, >=, 0);\n-\t\tif (ret > 0) {\n-\t\t\tcheck_int(i, ==, N);\n-\t\t\tbreak;\n-\t\t}\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tblock_iter_reset(&it);\n-\t\treftable_record_key(&recs[i], &want);\n-\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tcheck(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\n-\t\twant.len--;\n-\t\tret = block_iter_seek_key(&it, &br, &want);\n-\t\tcheck_int(ret, ==, 0);\n-\n-\t\tret = block_iter_next(&it, &rec);\n-\t\tcheck_int(ret, ==, 0);\n-\t\tcheck(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\tblock_reader_release(&br);\n-\tblock_iter_close(&it);\n-\treftable_record_release(&rec);\n-\treftable_block_done(&br.block);\n-\treftable_buf_release(&want);\n-\treftable_buf_release(&buf);\n-\tfor (i = 0; i < N; i++)\n-\t\treftable_record_release(&recs[i]);\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_index_block_read_write(), \"read-write operations on index blocks work\");\n-\tTEST(t_log_block_read_write(), \"read-write operations on log blocks work\");\n-\tTEST(t_obj_block_read_write(), \"read-write operations on obj blocks work\");\n-\tTEST(t_ref_block_read_write(), \"read-write operations on ref blocks work\");\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c\nnew file mode 100644\nindex 0000000000..af24901230\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-block.c\n@@ -0,0 +1,373 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"unit-test.h\"\n+#include \"reftable/block.h\"\n+#include \"reftable/blocksource.h\"\n+#include \"reftable/constants.h\"\n+#include \"reftable/reftable-error.h\"\n+#include \"strbuf.h\"\n+\n+void test_reftable_block__index_read_write(void)\n+{\n+\tconst int header_off = 21; /* random */\n+\tstruct reftable_record recs[30];\n+\tconst size_t N = ARRAY_SIZE(recs);\n+\tconst size_t block_size = 1024;\n+\tstruct reftable_block block = { 0 };\n+\tstruct block_writer bw = {\n+\t\t.last_key = REFTABLE_BUF_INIT,\n+\t};\n+\tstruct reftable_record rec = {\n+\t\t.type = BLOCK_TYPE_REF,\n+\t};\n+\tsize_t i = 0;\n+\tint ret;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = BLOCK_ITER_INIT;\n+\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n+\n+\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n+\tcl_assert(block.data != NULL);\n+\tblock.len = block_size;\n+\tblock_source_from_buf(&block.source ,&buf);\n+\tret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n+\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n+\tcl_assert(ret == 0);\n+\n+\trec.u.ref.refname = (char *) \"\";\n+\trec.u.ref.value_type = REFTABLE_REF_DELETION;\n+\tret = block_writer_add(&bw, &rec);\n+\tcl_assert_equal_i(ret, REFTABLE_API_ERROR);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\trec.u.ref.refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n+\t\trec.u.ref.value_type = REFTABLE_REF_VAL1;\n+\t\tmemset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);\n+\n+\t\trecs[i] = rec;\n+\t\tret = block_writer_add(&bw, &rec);\n+\t\trec.u.ref.refname = NULL;\n+\t\trec.u.ref.value_type = REFTABLE_REF_DELETION;\n+\t\tcl_assert_equal_i(ret, 0);\n+\t}\n+\n+\tret = block_writer_finish(&bw);\n+\tcl_assert(ret > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n+\n+\tblock_iter_seek_start(&it, &br);\n+\n+\tfor (i = 0; ; i++) {\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert(ret >= 0);\n+\t\tif (ret > 0) {\n+\t\t\tcl_assert_equal_i(i, N);\n+\t\t\tbreak;\n+\t\t}\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tblock_iter_reset(&it);\n+\t\treftable_record_key(&recs[i], &want);\n+\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\t\twant.len--;\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tblock_reader_release(&br);\n+\tblock_iter_close(&it);\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\treftable_buf_release(&want);\n+\treftable_buf_release(&buf);\n+\tfor (i = 0; i < N; i++)\n+\t\treftable_record_release(&recs[i]);\n+}\n+\n+void test_reftable_block__log_read_write(void)\n+{\n+\tconst int header_off = 21;\n+\tstruct reftable_record recs[30];\n+\tconst size_t N = ARRAY_SIZE(recs);\n+\tconst size_t block_size = 2048;\n+\tstruct reftable_block block = { 0 };\n+\tstruct block_writer bw = {\n+\t\t.last_key = REFTABLE_BUF_INIT,\n+\t};\n+\tstruct reftable_record rec = {\n+\t\t.type = BLOCK_TYPE_LOG,\n+\t};\n+\tsize_t i = 0;\n+\tint ret;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = BLOCK_ITER_INIT;\n+\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n+\n+\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n+\tcl_assert(block.data != NULL);\n+\tblock.len = block_size;\n+\tblock_source_from_buf(&block.source ,&buf);\n+\tret = block_writer_init(&bw, BLOCK_TYPE_LOG, block.data, block_size,\n+\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n+\tcl_assert(ret == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\trec.u.log.refname = xstrfmt(\"branch%02\"PRIuMAX , (uintmax_t)i);\n+\t\trec.u.log.update_index = i;\n+\t\trec.u.log.value_type = REFTABLE_LOG_UPDATE;\n+\n+\t\trecs[i] = rec;\n+\t\tret = block_writer_add(&bw, &rec);\n+\t\trec.u.log.refname = NULL;\n+\t\trec.u.log.value_type = REFTABLE_LOG_DELETION;\n+\t\tcl_assert_equal_i(ret, 0);\n+\t}\n+\n+\tret = block_writer_finish(&bw);\n+\tcl_assert(ret > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n+\n+\tblock_iter_seek_start(&it, &br);\n+\n+\tfor (i = 0; ; i++) {\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert(ret >= 0);\n+\t\tif (ret > 0) {\n+\t\t\tcl_assert_equal_i(i, N);\n+\t\t\tbreak;\n+\t\t}\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tblock_iter_reset(&it);\n+\t\treftable_buf_reset(&want);\n+\t\tcl_assert(reftable_buf_addstr(&want, recs[i].u.log.refname) == 0);\n+\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\t\twant.len--;\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tblock_reader_release(&br);\n+\tblock_iter_close(&it);\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\treftable_buf_release(&want);\n+\treftable_buf_release(&buf);\n+\tfor (i = 0; i < N; i++)\n+\t\treftable_record_release(&recs[i]);\n+}\n+\n+void test_reftable_block__obj_read_write(void)\n+{\n+\tconst int header_off = 21;\n+\tstruct reftable_record recs[30];\n+\tconst size_t N = ARRAY_SIZE(recs);\n+\tconst size_t block_size = 1024;\n+\tstruct reftable_block block = { 0 };\n+\tstruct block_writer bw = {\n+\t\t.last_key = REFTABLE_BUF_INIT,\n+\t};\n+\tstruct reftable_record rec = {\n+\t\t.type = BLOCK_TYPE_OBJ,\n+\t};\n+\tsize_t i = 0;\n+\tint ret;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = BLOCK_ITER_INIT;\n+\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n+\n+\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n+\tcl_assert(block.data != NULL);\n+\tblock.len = block_size;\n+\tblock_source_from_buf(&block.source, &buf);\n+\tret = block_writer_init(&bw, BLOCK_TYPE_OBJ, block.data, block_size,\n+\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n+\tcl_assert(ret == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t bytes[] = { i, i + 1, i + 2, i + 3, i + 5 }, *allocated;\n+\t\tDUP_ARRAY(allocated, bytes, ARRAY_SIZE(bytes));\n+\n+\t\trec.u.obj.hash_prefix = allocated;\n+\t\trec.u.obj.hash_prefix_len = 5;\n+\n+\t\trecs[i] = rec;\n+\t\tret = block_writer_add(&bw, &rec);\n+\t\trec.u.obj.hash_prefix = NULL;\n+\t\trec.u.obj.hash_prefix_len = 0;\n+\t\tcl_assert_equal_i(ret, 0);\n+\t}\n+\n+\tret = block_writer_finish(&bw);\n+\tcl_assert(ret > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n+\n+\tblock_iter_seek_start(&it, &br);\n+\n+\tfor (i = 0; ; i++) {\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert(ret >= 0);\n+\t\tif (ret > 0) {\n+\t\t\tcl_assert_equal_i(i, N);\n+\t\t\tbreak;\n+\t\t}\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tblock_iter_reset(&it);\n+\t\treftable_record_key(&recs[i], &want);\n+\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tblock_reader_release(&br);\n+\tblock_iter_close(&it);\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\treftable_buf_release(&want);\n+\treftable_buf_release(&buf);\n+\tfor (i = 0; i < N; i++)\n+\t\treftable_record_release(&recs[i]);\n+}\n+\n+void test_reftable_block__ref_read_write(void)\n+{\n+\tconst int header_off = 21;\n+\tstruct reftable_record recs[30];\n+\tconst size_t N = ARRAY_SIZE(recs);\n+\tconst size_t block_size = 1024;\n+\tstruct reftable_block block = { 0 };\n+\tstruct block_writer bw = {\n+\t\t.last_key = REFTABLE_BUF_INIT,\n+\t};\n+\tstruct reftable_record rec = {\n+\t\t.type = BLOCK_TYPE_INDEX,\n+\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n+\t};\n+\tsize_t i = 0;\n+\tint ret;\n+\tstruct block_reader br = { 0 };\n+\tstruct block_iter it = BLOCK_ITER_INIT;\n+\tstruct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n+\n+\tREFTABLE_CALLOC_ARRAY(block.data, block_size);\n+\tcl_assert(block.data != NULL);\n+\tblock.len = block_size;\n+\tblock_source_from_buf(&block.source, &buf);\n+\tret = block_writer_init(&bw, BLOCK_TYPE_INDEX, block.data, block_size,\n+\t\t\t\theader_off, hash_size(REFTABLE_HASH_SHA1));\n+\tcl_assert(ret == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar buf[128];\n+\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02\"PRIuMAX, (uintmax_t)i);\n+\n+\t\treftable_buf_init(&recs[i].u.idx.last_key);\n+\t\trecs[i].type = BLOCK_TYPE_INDEX;\n+\t\tcl_assert(reftable_buf_addstr(&recs[i].u.idx.last_key, buf) == 0);\n+\t\trecs[i].u.idx.offset = i;\n+\n+\t\tret = block_writer_add(&bw, &recs[i]);\n+\t\tcl_assert_equal_i(ret, 0);\n+\t}\n+\n+\tret = block_writer_finish(&bw);\n+\tcl_assert(ret > 0);\n+\n+\tblock_writer_release(&bw);\n+\n+\tblock_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);\n+\n+\tblock_iter_seek_start(&it, &br);\n+\n+\tfor (i = 0; ; i++) {\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert(ret >= 0);\n+\t\tif (ret > 0) {\n+\t\t\tcl_assert_equal_i(i, N);\n+\t\t\tbreak;\n+\t\t}\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tblock_iter_reset(&it);\n+\t\treftable_record_key(&recs[i], &want);\n+\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\t\twant.len--;\n+\t\tret = block_iter_seek_key(&it, &br, &want);\n+\t\tcl_assert_equal_i(ret, 0);\n+\n+\t\tret = block_iter_next(&it, &rec);\n+\t\tcl_assert_equal_i(ret, 0);\n+\t\tcl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);\n+\t}\n+\n+\tblock_reader_release(&br);\n+\tblock_iter_close(&it);\n+\treftable_record_release(&rec);\n+\treftable_block_done(&br.block);\n+\treftable_buf_release(&want);\n+\treftable_buf_release(&buf);\n+\tfor (i = 0; i < N; i++)\n+\t\treftable_record_release(&recs[i]);\n+}\n-- \n2.43.0\n\n"},{"id":"516859","messageId":"20250427185351.82520-5-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 4/9] t/unit-tests: convert reftable merged test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:46Z","receivedAt":"2025-04-27T18:54:49Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable merged test file to use clar testing framework by using\nclar assertions where necessary.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                         |   2 +-\n t/meson.build                    |   2 +-\n t/unit-tests/t-reftable-merged.c | 546 -------------------------------\n t/unit-tests/u-reftable-merged.c | 515 +++++++++++++++++++++++++++++\n 4 files changed, 517 insertions(+), 548 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-merged.c\n create mode 100644 t/unit-tests/u-reftable-merged.c\n\ndiff --git a/Makefile b/Makefile\nindex 3fc012f86a..a7a519ac81 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1364,6 +1364,7 @@ CLAR_TEST_SUITES += u-oidtree\n CLAR_TEST_SUITES += u-prio-queue\n CLAR_TEST_SUITES += u-reftable-basics\n CLAR_TEST_SUITES += u-reftable-block\n+CLAR_TEST_SUITES += u-reftable-merged\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1377,7 +1378,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-merged\n UNIT_TEST_PROGRAMS += t-reftable-pq\n UNIT_TEST_PROGRAMS += t-reftable-reader\n UNIT_TEST_PROGRAMS += t-reftable-readwrite\ndiff --git a/t/meson.build b/t/meson.build\nindex eb9e69eb27..a4210dc561 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -10,6 +10,7 @@ clar_test_suites = [\n   'unit-tests/u-prio-queue.c',\n   'unit-tests/u-reftable-basics.c',\n   'unit-tests/u-reftable-block.c',\n+  'unit-tests/u-reftable-merged.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -57,7 +58,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-merged.c',\n   'unit-tests/t-reftable-pq.c',\n   'unit-tests/t-reftable-reader.c',\n   'unit-tests/t-reftable-readwrite.c',\ndiff --git a/t/unit-tests/t-reftable-merged.c b/t/unit-tests/t-reftable-merged.c\ndeleted file mode 100644\nindex 60836f80d6..0000000000\n--- a/t/unit-tests/t-reftable-merged.c\n+++ /dev/null\n@@ -1,546 +0,0 @@\n-/*\n-Copyright 2020 Google LLC\n-\n-Use of this source code is governed by a BSD-style\n-license that can be found in the LICENSE file or at\n-https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#include \"test-lib.h\"\n-#include \"lib-reftable.h\"\n-#include \"reftable/blocksource.h\"\n-#include \"reftable/constants.h\"\n-#include \"reftable/merged.h\"\n-#include \"reftable/reader.h\"\n-#include \"reftable/reftable-error.h\"\n-#include \"reftable/reftable-merged.h\"\n-#include \"reftable/reftable-writer.h\"\n-\n-static struct reftable_merged_table *\n-merged_table_from_records(struct reftable_ref_record **refs,\n-\t\t\t  struct reftable_block_source **source,\n-\t\t\t  struct reftable_reader ***readers, const size_t *sizes,\n-\t\t\t  struct reftable_buf *buf, const size_t n)\n-{\n-\tstruct reftable_merged_table *mt = NULL;\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 256,\n-\t};\n-\tint err;\n-\n-\tREFTABLE_CALLOC_ARRAY(*readers, n);\n-\tcheck(*readers != NULL);\n-\tREFTABLE_CALLOC_ARRAY(*source, n);\n-\tcheck(*source != NULL);\n-\n-\tfor (size_t i = 0; i < n; i++) {\n-\t\tt_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);\n-\t\tblock_source_from_buf(&(*source)[i], &buf[i]);\n-\n-\t\terr = reftable_reader_new(&(*readers)[i], &(*source)[i],\n-\t\t\t\t\t  \"name\");\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);\n-\tcheck(!err);\n-\treturn mt;\n-}\n-\n-static void readers_destroy(struct reftable_reader **readers, const size_t n)\n-{\n-\tfor (size_t i = 0; i < n; i++)\n-\t\treftable_reader_decref(readers[i]);\n-\treftable_free(readers);\n-}\n-\n-static void t_merged_single_record(void)\n-{\n-\tstruct reftable_ref_record r1[] = { {\n-\t\t.refname = (char *) \"b\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t.value.val1 = { 1, 2, 3, 0 },\n-\t} };\n-\tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = (char *) \"a\",\n-\t\t.update_index = 2,\n-\t\t.value_type = REFTABLE_REF_DELETION,\n-\t} };\n-\tstruct reftable_ref_record r3[] = { {\n-\t\t.refname = (char *) \"c\",\n-\t\t.update_index = 3,\n-\t\t.value_type = REFTABLE_REF_DELETION,\n-\t} };\n-\n-\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n-\tsize_t sizes[] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };\n-\tstruct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };\n-\tstruct reftable_block_source *bs = NULL;\n-\tstruct reftable_reader **readers = NULL;\n-\tstruct reftable_merged_table *mt =\n-\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n-\tstruct reftable_ref_record ref = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tint err;\n-\n-\terr = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, \"a\");\n-\tcheck(!err);\n-\n-\terr = reftable_iterator_next_ref(&it, &ref);\n-\tcheck(!err);\n-\tcheck(reftable_ref_record_equal(&r2[0], &ref, REFTABLE_HASH_SIZE_SHA1));\n-\treftable_ref_record_release(&ref);\n-\treftable_iterator_destroy(&it);\n-\treaders_destroy(readers, 3);\n-\treftable_merged_table_free(mt);\n-\tfor (size_t i = 0; i < ARRAY_SIZE(bufs); i++)\n-\t\treftable_buf_release(&bufs[i]);\n-\treftable_free(bs);\n-}\n-\n-static void t_merged_refs(void)\n-{\n-\tstruct reftable_ref_record r1[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 1 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"b\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 1 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"c\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 1 },\n-\t\t}\n-\t};\n-\tstruct reftable_ref_record r2[] = { {\n-\t\t.refname = (char *) \"a\",\n-\t\t.update_index = 2,\n-\t\t.value_type = REFTABLE_REF_DELETION,\n-\t} };\n-\tstruct reftable_ref_record r3[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"c\",\n-\t\t\t.update_index = 3,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 2 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"d\",\n-\t\t\t.update_index = 3,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 1 },\n-\t\t},\n-\t};\n-\n-\tstruct reftable_ref_record *want[] = {\n-\t\t&r2[0],\n-\t\t&r1[1],\n-\t\t&r3[0],\n-\t\t&r3[1],\n-\t};\n-\n-\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n-\tsize_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };\n-\tstruct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };\n-\tstruct reftable_block_source *bs = NULL;\n-\tstruct reftable_reader **readers = NULL;\n-\tstruct reftable_merged_table *mt =\n-\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n-\tstruct reftable_iterator it = { 0 };\n-\tint err;\n-\tstruct reftable_ref_record *out = NULL;\n-\tsize_t len = 0;\n-\tsize_t cap = 0;\n-\tsize_t i;\n-\n-\terr = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, \"a\");\n-\tcheck(!err);\n-\tcheck_int(reftable_merged_table_hash_id(mt), ==, REFTABLE_HASH_SHA1);\n-\tcheck_int(reftable_merged_table_min_update_index(mt), ==, 1);\n-\tcheck_int(reftable_merged_table_max_update_index(mt), ==, 3);\n-\n-\twhile (len < 100) { /* cap loops/recursion. */\n-\t\tstruct reftable_ref_record ref = { 0 };\n-\t\tint err = reftable_iterator_next_ref(&it, &ref);\n-\t\tif (err > 0)\n-\t\t\tbreak;\n-\n-\t\tcheck(!REFTABLE_ALLOC_GROW(out, len + 1, cap));\n-\t\tout[len++] = ref;\n-\t}\n-\treftable_iterator_destroy(&it);\n-\n-\tcheck_int(ARRAY_SIZE(want), ==, len);\n-\tfor (i = 0; i < len; i++)\n-\t\tcheck(reftable_ref_record_equal(want[i], &out[i],\n-\t\t\t\t\t\t REFTABLE_HASH_SIZE_SHA1));\n-\tfor (i = 0; i < len; i++)\n-\t\treftable_ref_record_release(&out[i]);\n-\treftable_free(out);\n-\n-\tfor (i = 0; i < 3; i++)\n-\t\treftable_buf_release(&bufs[i]);\n-\treaders_destroy(readers, 3);\n-\treftable_merged_table_free(mt);\n-\treftable_free(bs);\n-}\n-\n-static void t_merged_seek_multiple_times(void)\n-{\n-\tstruct reftable_ref_record r1[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 1 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"c\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 2 },\n-\t\t}\n-\t};\n-\tstruct reftable_ref_record r2[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"b\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 3 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"d\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 4 },\n-\t\t},\n-\t};\n-\tstruct reftable_ref_record *refs[] = {\n-\t\tr1, r2,\n-\t};\n-\tsize_t sizes[] = {\n-\t\tARRAY_SIZE(r1), ARRAY_SIZE(r2),\n-\t};\n-\tstruct reftable_buf bufs[] = {\n-\t\tREFTABLE_BUF_INIT, REFTABLE_BUF_INIT,\n-\t};\n-\tstruct reftable_block_source *sources = NULL;\n-\tstruct reftable_reader **readers = NULL;\n-\tstruct reftable_ref_record rec = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_merged_table *mt;\n-\n-\tmt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);\n-\tmerged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n-\n-\tfor (size_t i = 0; i < 5; i++) {\n-\t\tint err = reftable_iterator_seek_ref(&it, \"c\");\n-\t\tcheck(!err);\n-\n-\t\terr = reftable_iterator_next_ref(&it, &rec);\n-\t\tcheck(!err);\n-\t\terr = reftable_ref_record_equal(&rec, &r1[1], REFTABLE_HASH_SIZE_SHA1);\n-\t\tcheck(err == 1);\n-\n-\t\terr = reftable_iterator_next_ref(&it, &rec);\n-\t\tcheck(!err);\n-\t\terr = reftable_ref_record_equal(&rec, &r2[1], REFTABLE_HASH_SIZE_SHA1);\n-\t\tcheck(err == 1);\n-\n-\t\terr = reftable_iterator_next_ref(&it, &rec);\n-\t\tcheck(err > 0);\n-\t}\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(bufs); i++)\n-\t\treftable_buf_release(&bufs[i]);\n-\treaders_destroy(readers, ARRAY_SIZE(refs));\n-\treftable_ref_record_release(&rec);\n-\treftable_iterator_destroy(&it);\n-\treftable_merged_table_free(mt);\n-\treftable_free(sources);\n-}\n-\n-static void t_merged_seek_multiple_times_without_draining(void)\n-{\n-\tstruct reftable_ref_record r1[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 1 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"c\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 2 },\n-\t\t}\n-\t};\n-\tstruct reftable_ref_record r2[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"b\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 3 },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"d\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 4 },\n-\t\t},\n-\t};\n-\tstruct reftable_ref_record *refs[] = {\n-\t\tr1, r2,\n-\t};\n-\tsize_t sizes[] = {\n-\t\tARRAY_SIZE(r1), ARRAY_SIZE(r2),\n-\t};\n-\tstruct reftable_buf bufs[] = {\n-\t\tREFTABLE_BUF_INIT, REFTABLE_BUF_INIT,\n-\t};\n-\tstruct reftable_block_source *sources = NULL;\n-\tstruct reftable_reader **readers = NULL;\n-\tstruct reftable_ref_record rec = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_merged_table *mt;\n-\tint err;\n-\n-\tmt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);\n-\tmerged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n-\n-\terr = reftable_iterator_seek_ref(&it, \"b\");\n-\tcheck(!err);\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck(!err);\n-\terr = reftable_ref_record_equal(&rec, &r2[0], REFTABLE_HASH_SIZE_SHA1);\n-\tcheck(err == 1);\n-\n-\terr = reftable_iterator_seek_ref(&it, \"a\");\n-\tcheck(!err);\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck(!err);\n-\terr = reftable_ref_record_equal(&rec, &r1[0], REFTABLE_HASH_SIZE_SHA1);\n-\tcheck(err == 1);\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(bufs); i++)\n-\t\treftable_buf_release(&bufs[i]);\n-\treaders_destroy(readers, ARRAY_SIZE(refs));\n-\treftable_ref_record_release(&rec);\n-\treftable_iterator_destroy(&it);\n-\treftable_merged_table_free(mt);\n-\treftable_free(sources);\n-}\n-\n-static struct reftable_merged_table *\n-merged_table_from_log_records(struct reftable_log_record **logs,\n-\t\t\t      struct reftable_block_source **source,\n-\t\t\t      struct reftable_reader ***readers, const size_t *sizes,\n-\t\t\t      struct reftable_buf *buf, const size_t n)\n-{\n-\tstruct reftable_merged_table *mt = NULL;\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 256,\n-\t\t.exact_log_message = 1,\n-\t};\n-\tint err;\n-\n-\tREFTABLE_CALLOC_ARRAY(*readers, n);\n-\tcheck(*readers != NULL);\n-\tREFTABLE_CALLOC_ARRAY(*source, n);\n-\tcheck(*source != NULL);\n-\n-\tfor (size_t i = 0; i < n; i++) {\n-\t\tt_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts);\n-\t\tblock_source_from_buf(&(*source)[i], &buf[i]);\n-\n-\t\terr = reftable_reader_new(&(*readers)[i], &(*source)[i],\n-\t\t\t\t\t  \"name\");\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);\n-\tcheck(!err);\n-\treturn mt;\n-}\n-\n-static void t_merged_logs(void)\n-{\n-\tstruct reftable_log_record r1[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value.update = {\n-\t\t\t\t.old_hash = { 2 },\n-\t\t\t\t/* deletion */\n-\t\t\t\t.name = (char *) \"jane doe\",\n-\t\t\t\t.email = (char *) \"jane@invalid\",\n-\t\t\t\t.message = (char *) \"message2\",\n-\t\t\t}\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value.update = {\n-\t\t\t\t.old_hash = { 1 },\n-\t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = (char *) \"jane doe\",\n-\t\t\t\t.email = (char *) \"jane@invalid\",\n-\t\t\t\t.message = (char *) \"message1\",\n-\t\t\t}\n-\t\t},\n-\t};\n-\tstruct reftable_log_record r2[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 3,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value.update = {\n-\t\t\t\t.new_hash = { 3 },\n-\t\t\t\t.name = (char *) \"jane doe\",\n-\t\t\t\t.email = (char *) \"jane@invalid\",\n-\t\t\t\t.message = (char *) \"message3\",\n-\t\t\t}\n-\t\t},\n-\t};\n-\tstruct reftable_log_record r3[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_LOG_DELETION,\n-\t\t},\n-\t};\n-\tstruct reftable_log_record *want[] = {\n-\t\t&r2[0],\n-\t\t&r3[0],\n-\t\t&r1[1],\n-\t};\n-\n-\tstruct reftable_log_record *logs[] = { r1, r2, r3 };\n-\tsize_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };\n-\tstruct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };\n-\tstruct reftable_block_source *bs = NULL;\n-\tstruct reftable_reader **readers = NULL;\n-\tstruct reftable_merged_table *mt = merged_table_from_log_records(\n-\t\tlogs, &bs, &readers, sizes, bufs, 3);\n-\tstruct reftable_iterator it = { 0 };\n-\tint err;\n-\tstruct reftable_log_record *out = NULL;\n-\tsize_t len = 0;\n-\tsize_t cap = 0;\n-\tsize_t i;\n-\n-\terr = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_log(&it, \"a\");\n-\tcheck(!err);\n-\tcheck_int(reftable_merged_table_hash_id(mt), ==, REFTABLE_HASH_SHA1);\n-\tcheck_int(reftable_merged_table_min_update_index(mt), ==, 1);\n-\tcheck_int(reftable_merged_table_max_update_index(mt), ==, 3);\n-\n-\twhile (len < 100) { /* cap loops/recursion. */\n-\t\tstruct reftable_log_record log = { 0 };\n-\t\tint err = reftable_iterator_next_log(&it, &log);\n-\t\tif (err > 0)\n-\t\t\tbreak;\n-\n-\t\tcheck(!REFTABLE_ALLOC_GROW(out, len + 1, cap));\n-\t\tout[len++] = log;\n-\t}\n-\treftable_iterator_destroy(&it);\n-\n-\tcheck_int(ARRAY_SIZE(want), ==, len);\n-\tfor (i = 0; i < len; i++)\n-\t\tcheck(reftable_log_record_equal(want[i], &out[i],\n-\t\t\t\t\t\t REFTABLE_HASH_SIZE_SHA1));\n-\n-\terr = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_log_at(&it, \"a\", 2);\n-\tcheck(!err);\n-\treftable_log_record_release(&out[0]);\n-\terr = reftable_iterator_next_log(&it, &out[0]);\n-\tcheck(!err);\n-\tcheck(reftable_log_record_equal(&out[0], &r3[0], REFTABLE_HASH_SIZE_SHA1));\n-\treftable_iterator_destroy(&it);\n-\n-\tfor (i = 0; i < len; i++)\n-\t\treftable_log_record_release(&out[i]);\n-\treftable_free(out);\n-\n-\tfor (i = 0; i < 3; i++)\n-\t\treftable_buf_release(&bufs[i]);\n-\treaders_destroy(readers, 3);\n-\treftable_merged_table_free(mt);\n-\treftable_free(bs);\n-}\n-\n-static void t_default_write_opts(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_ref_record rec = {\n-\t\t.refname = (char *) \"master\",\n-\t\t.update_index = 1,\n-\t};\n-\tint err;\n-\tstruct reftable_block_source source = { 0 };\n-\tuint32_t hash_id;\n-\tstruct reftable_reader *rd = NULL;\n-\tstruct reftable_merged_table *merged = NULL;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\n-\terr = reftable_writer_add_ref(w, &rec);\n-\tcheck(!err);\n-\n-\terr = reftable_writer_close(w);\n-\tcheck(!err);\n-\treftable_writer_free(w);\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&rd, &source, \"filename\");\n-\tcheck(!err);\n-\n-\thash_id = reftable_reader_hash_id(rd);\n-\tcheck_int(hash_id, ==, REFTABLE_HASH_SHA1);\n-\n-\terr = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA256);\n-\tcheck_int(err, ==, REFTABLE_FORMAT_ERROR);\n-\terr = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA1);\n-\tcheck(!err);\n-\n-\treftable_reader_decref(rd);\n-\treftable_merged_table_free(merged);\n-\treftable_buf_release(&buf);\n-}\n-\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_default_write_opts(), \"merged table with default write opts\");\n-\tTEST(t_merged_logs(), \"merged table with multiple log updates for same ref\");\n-\tTEST(t_merged_refs(), \"merged table with multiple updates to same ref\");\n-\tTEST(t_merged_seek_multiple_times(), \"merged table can seek multiple times\");\n-\tTEST(t_merged_seek_multiple_times_without_draining(), \"merged table can seek multiple times without draining\");\n-\tTEST(t_merged_single_record(), \"ref occurring in only one record can be fetched\");\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c\nnew file mode 100644\nindex 0000000000..48c8f9f6b5\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-merged.c\n@@ -0,0 +1,515 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"unit-test.h\"\n+#include \"lib-reftable.h\"\n+#include \"reftable/blocksource.h\"\n+#include \"reftable/constants.h\"\n+#include \"reftable/merged.h\"\n+#include \"reftable/reader.h\"\n+#include \"reftable/reftable-error.h\"\n+#include \"reftable/reftable-merged.h\"\n+#include \"reftable/reftable-writer.h\"\n+\n+static struct reftable_merged_table *\n+merged_table_from_records(struct reftable_ref_record **refs,\n+\t\t\t  struct reftable_block_source **source,\n+\t\t\t  struct reftable_reader ***readers, const size_t *sizes,\n+\t\t\t  struct reftable_buf *buf, const size_t n)\n+{\n+\tstruct reftable_merged_table *mt = NULL;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tint err;\n+\n+\tREFTABLE_CALLOC_ARRAY(*readers, n);\n+\tcl_assert(*readers != NULL);\n+\tREFTABLE_CALLOC_ARRAY(*source, n);\n+\tcl_assert(*source != NULL);\n+\n+\tfor (size_t i = 0; i < n; i++) {\n+\t\tcl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);\n+\t\tblock_source_from_buf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_reader_new(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tcl_assert(err == 0);\n+\t}\n+\n+\terr = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);\n+\tcl_assert(err == 0);\n+\treturn mt;\n+}\n+\n+static void readers_destroy(struct reftable_reader **readers, const size_t n)\n+{\n+\tfor (size_t i = 0; i < n; i++)\n+\t\treftable_reader_decref(readers[i]);\n+\treftable_free(readers);\n+}\n+\n+void test_reftable_merged__merged_single_record(void)\n+{\n+\tstruct reftable_ref_record r1[] = { {\n+\t\t.refname = (char *) \"b\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = { 1, 2, 3, 0 },\n+\t} };\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = (char *) \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\tstruct reftable_ref_record r3[] = { {\n+\t\t.refname = (char *) \"c\",\n+\t\t.update_index = 3,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n+\tsize_t sizes[] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };\n+\tstruct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n+\tstruct reftable_ref_record ref = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tint err;\n+\n+\terr = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n+\tcl_assert(err == 0);\n+\terr = reftable_iterator_seek_ref(&it, \"a\");\n+\tcl_assert(err == 0);\n+\n+\terr = reftable_iterator_next_ref(&it, &ref);\n+\tcl_assert(err == 0);\n+\tcl_assert(reftable_ref_record_equal(&r2[0], &ref, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\tfor (size_t i = 0; i < ARRAY_SIZE(bufs); i++)\n+\t\treftable_buf_release(&bufs[i]);\n+\treftable_free(bs);\n+}\n+\n+void test_reftable_merged__merged_refs(void)\n+{\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 1 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 1 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 1 },\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = { {\n+\t\t.refname = (char *) \"a\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t} };\n+\tstruct reftable_ref_record r3[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"c\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 2 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"d\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 1 },\n+\t\t},\n+\t};\n+\n+\tstruct reftable_ref_record *want[] = {\n+\t\t&r2[0],\n+\t\t&r1[1],\n+\t\t&r3[0],\n+\t\t&r3[1],\n+\t};\n+\n+\tstruct reftable_ref_record *refs[] = { r1, r2, r3 };\n+\tsize_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };\n+\tstruct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt =\n+\t\tmerged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);\n+\tstruct reftable_iterator it = { 0 };\n+\tint err;\n+\tstruct reftable_ref_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tsize_t i;\n+\n+\terr = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n+\tcl_assert(err == 0);\n+\terr = reftable_iterator_seek_ref(&it, \"a\");\n+\tcl_assert(err == 0);\n+\tcl_assert_equal_i(reftable_merged_table_hash_id(mt), REFTABLE_HASH_SHA1);\n+\tcl_assert_equal_i(reftable_merged_table_min_update_index(mt), 1);\n+\tcl_assert_equal_i(reftable_merged_table_max_update_index(mt), 3);\n+\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_ref_record ref = { 0 };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0)\n+\t\t\tbreak;\n+\n+\t\tcl_assert(REFTABLE_ALLOC_GROW(out, len + 1, cap) == 0);\n+\t\tout[len++] = ref;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tcl_assert_equal_i(ARRAY_SIZE(want), len);\n+\tfor (i = 0; i < len; i++)\n+\t\tcl_assert(reftable_ref_record_equal(want[i], &out[i],\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\tfor (i = 0; i < len; i++)\n+\t\treftable_ref_record_release(&out[i]);\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\treftable_buf_release(&bufs[i]);\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+void test_reftable_merged__merged_seek_multiple_times(void)\n+{\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 1 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 2 },\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"b\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 3 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"d\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 4 },\n+\t\t},\n+\t};\n+\tstruct reftable_ref_record *refs[] = {\n+\t\tr1, r2,\n+\t};\n+\tsize_t sizes[] = {\n+\t\tARRAY_SIZE(r1), ARRAY_SIZE(r2),\n+\t};\n+\tstruct reftable_buf bufs[] = {\n+\t\tREFTABLE_BUF_INIT, REFTABLE_BUF_INIT,\n+\t};\n+\tstruct reftable_block_source *sources = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_ref_record rec = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_merged_table *mt;\n+\n+\tmt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);\n+\tmerged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n+\n+\tfor (size_t i = 0; i < 5; i++) {\n+\t\tint err = reftable_iterator_seek_ref(&it, \"c\");\n+\t\tcl_assert(err == 0);\n+\n+\t\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\t\tcl_assert_equal_i(reftable_ref_record_equal(&rec, &r1[1],\n+\t\t\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\t\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\t\tcl_assert_equal_i(reftable_ref_record_equal(&rec, &r2[1],\n+\t\t\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\t\tcl_assert(reftable_iterator_next_ref(&it, &rec) > 0);\n+\t}\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(bufs); i++)\n+\t\treftable_buf_release(&bufs[i]);\n+\treaders_destroy(readers, ARRAY_SIZE(refs));\n+\treftable_ref_record_release(&rec);\n+\treftable_iterator_destroy(&it);\n+\treftable_merged_table_free(mt);\n+\treftable_free(sources);\n+}\n+\n+void test_reftable_merged__merged_seek_multiple_times_no_drain(void)\n+{\n+\tstruct reftable_ref_record r1[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 1 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"c\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 2 },\n+\t\t}\n+\t};\n+\tstruct reftable_ref_record r2[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"b\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 3 },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"d\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 4 },\n+\t\t},\n+\t};\n+\tstruct reftable_ref_record *refs[] = {\n+\t\tr1, r2,\n+\t};\n+\tsize_t sizes[] = {\n+\t\tARRAY_SIZE(r1), ARRAY_SIZE(r2),\n+\t};\n+\tstruct reftable_buf bufs[] = {\n+\t\tREFTABLE_BUF_INIT, REFTABLE_BUF_INIT,\n+\t};\n+\tstruct reftable_block_source *sources = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_ref_record rec = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_merged_table *mt;\n+\n+\tmt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);\n+\tmerged_table_init_iter(mt, &it, BLOCK_TYPE_REF);\n+\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"b\") == 0);\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\tcl_assert_equal_i(reftable_ref_record_equal(&rec, &r2[0],\n+\t\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"a\") == 0);\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\tcl_assert_equal_i(reftable_ref_record_equal(&rec, &r1[0],\n+\t\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(bufs); i++)\n+\t\treftable_buf_release(&bufs[i]);\n+\treaders_destroy(readers, ARRAY_SIZE(refs));\n+\treftable_ref_record_release(&rec);\n+\treftable_iterator_destroy(&it);\n+\treftable_merged_table_free(mt);\n+\treftable_free(sources);\n+}\n+\n+static struct reftable_merged_table *\n+merged_table_from_log_records(struct reftable_log_record **logs,\n+\t\t\t      struct reftable_block_source **source,\n+\t\t\t      struct reftable_reader ***readers, const size_t *sizes,\n+\t\t\t      struct reftable_buf *buf, const size_t n)\n+{\n+\tstruct reftable_merged_table *mt = NULL;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t\t.exact_log_message = 1,\n+\t};\n+\tint err;\n+\n+\tREFTABLE_CALLOC_ARRAY(*readers, n);\n+\tcl_assert(*readers != NULL);\n+\tREFTABLE_CALLOC_ARRAY(*source, n);\n+\tcl_assert(*source != NULL);\n+\n+\tfor (size_t i = 0; i < n; i++) {\n+\t\tcl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts);\n+\t\tblock_source_from_buf(&(*source)[i], &buf[i]);\n+\n+\t\terr = reftable_reader_new(&(*readers)[i], &(*source)[i],\n+\t\t\t\t\t  \"name\");\n+\t\tcl_assert(err == 0);\n+\t}\n+\n+\tcl_assert(reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1) == 0);\n+\treturn mt;\n+}\n+\n+void test_reftable_merged__merged_logs(void)\n+{\n+\tstruct reftable_log_record r1[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.old_hash = { 2 },\n+\t\t\t\t/* deletion */\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message2\",\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.old_hash = { 1 },\n+\t\t\t\t.new_hash = { 2 },\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message1\",\n+\t\t\t}\n+\t\t},\n+\t};\n+\tstruct reftable_log_record r2[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 3,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.new_hash = { 3 },\n+\t\t\t\t.name = (char *) \"jane doe\",\n+\t\t\t\t.email = (char *) \"jane@invalid\",\n+\t\t\t\t.message = (char *) \"message3\",\n+\t\t\t}\n+\t\t},\n+\t};\n+\tstruct reftable_log_record r3[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t},\n+\t};\n+\tstruct reftable_log_record *want[] = {\n+\t\t&r2[0],\n+\t\t&r3[0],\n+\t\t&r1[1],\n+\t};\n+\n+\tstruct reftable_log_record *logs[] = { r1, r2, r3 };\n+\tsize_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };\n+\tstruct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };\n+\tstruct reftable_block_source *bs = NULL;\n+\tstruct reftable_reader **readers = NULL;\n+\tstruct reftable_merged_table *mt = merged_table_from_log_records(\n+\t\tlogs, &bs, &readers, sizes, bufs, 3);\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_log_record *out = NULL;\n+\tsize_t len = 0;\n+\tsize_t cap = 0;\n+\tsize_t i;\n+\n+\tcl_assert(merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG) == 0);\n+\tcl_assert(reftable_iterator_seek_log(&it, \"a\") == 0);\n+\tcl_assert_equal_i(reftable_merged_table_hash_id(mt), REFTABLE_HASH_SHA1);\n+\tcl_assert_equal_i(reftable_merged_table_min_update_index(mt), 1);\n+\tcl_assert_equal_i(reftable_merged_table_max_update_index(mt), 3);\n+\n+\twhile (len < 100) { /* cap loops/recursion. */\n+\t\tstruct reftable_log_record log = { 0 };\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0)\n+\t\t\tbreak;\n+\n+\t\tcl_assert(REFTABLE_ALLOC_GROW(out, len + 1, cap) == 0);\n+\t\tout[len++] = log;\n+\t}\n+\treftable_iterator_destroy(&it);\n+\n+\tcl_assert_equal_i(ARRAY_SIZE(want), len);\n+\tfor (i = 0; i < len; i++)\n+\t\tcl_assert(reftable_log_record_equal(want[i], &out[i],\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\n+\tcl_assert(merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG) == 0);\n+\tcl_assert(reftable_iterator_seek_log_at(&it, \"a\", 2) == 0);\n+\treftable_log_record_release(&out[0]);\n+\tcl_assert(reftable_iterator_next_log(&it, &out[0]) == 0);\n+\tcl_assert(reftable_log_record_equal(&out[0], &r3[0],\n+\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\treftable_iterator_destroy(&it);\n+\n+\tfor (i = 0; i < len; i++)\n+\t\treftable_log_record_release(&out[i]);\n+\treftable_free(out);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\treftable_buf_release(&bufs[i]);\n+\treaders_destroy(readers, 3);\n+\treftable_merged_table_free(mt);\n+\treftable_free(bs);\n+}\n+\n+void test_reftable_merged__default_write_opts(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_ref_record rec = {\n+\t\t.refname = (char *) \"master\",\n+\t\t.update_index = 1,\n+\t};\n+\tint err;\n+\tstruct reftable_block_source source = { 0 };\n+\tuint32_t hash_id;\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_merged_table *merged = NULL;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\tcl_assert(reftable_writer_add_ref(w, &rec) == 0);\n+\n+\tcl_assert(reftable_writer_close(w) == 0);\n+\treftable_writer_free(w);\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&rd, &source, \"filename\") == 0);\n+\n+\thash_id = reftable_reader_hash_id(rd);\n+\tcl_assert_equal_i(hash_id, REFTABLE_HASH_SHA1);\n+\n+\terr = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA256);\n+\tcl_assert_equal_i(err, REFTABLE_FORMAT_ERROR);\n+\tcl_assert(reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA1) == 0);\n+\n+\treftable_reader_decref(rd);\n+\treftable_merged_table_free(merged);\n+\treftable_buf_release(&buf);\n+}\n-- \n2.43.0\n\n"},{"id":"516860","messageId":"20250427185351.82520-6-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 5/9] t/unit-tests: convert reftable pq test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:47Z","receivedAt":"2025-04-27T18:54:55Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable priority queue test file to use clar by using clar\nassertions where necessary.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                     |   2 +-\n t/meson.build                |   2 +-\n t/unit-tests/t-reftable-pq.c | 161 -----------------------------------\n t/unit-tests/u-reftable-pq.c | 152 +++++++++++++++++++++++++++++++++\n 4 files changed, 154 insertions(+), 163 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-pq.c\n create mode 100644 t/unit-tests/u-reftable-pq.c\n\ndiff --git a/Makefile b/Makefile\nindex a7a519ac81..060d89df7f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1365,6 +1365,7 @@ CLAR_TEST_SUITES += u-prio-queue\n CLAR_TEST_SUITES += u-reftable-basics\n CLAR_TEST_SUITES += u-reftable-block\n CLAR_TEST_SUITES += u-reftable-merged\n+CLAR_TEST_SUITES += u-reftable-pq\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1378,7 +1379,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-pq\n UNIT_TEST_PROGRAMS += t-reftable-reader\n UNIT_TEST_PROGRAMS += t-reftable-readwrite\n UNIT_TEST_PROGRAMS += t-reftable-record\ndiff --git a/t/meson.build b/t/meson.build\nindex a4210dc561..1e12bd1b96 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -11,6 +11,7 @@ clar_test_suites = [\n   'unit-tests/u-reftable-basics.c',\n   'unit-tests/u-reftable-block.c',\n   'unit-tests/u-reftable-merged.c',\n+  'unit-tests/u-reftable-pq.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -58,7 +59,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-pq.c',\n   'unit-tests/t-reftable-reader.c',\n   'unit-tests/t-reftable-readwrite.c',\n   'unit-tests/t-reftable-record.c',\ndiff --git a/t/unit-tests/t-reftable-pq.c b/t/unit-tests/t-reftable-pq.c\ndeleted file mode 100644\nindex c128fe8616..0000000000\n--- a/t/unit-tests/t-reftable-pq.c\n+++ /dev/null\n@@ -1,161 +0,0 @@\n-/*\n-Copyright 2020 Google LLC\n-\n-Use of this source code is governed by a BSD-style\n-license that can be found in the LICENSE file or at\n-https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#include \"test-lib.h\"\n-#include \"reftable/constants.h\"\n-#include \"reftable/pq.h\"\n-#include \"strbuf.h\"\n-\n-static void merged_iter_pqueue_check(const struct merged_iter_pqueue *pq)\n-{\n-\tfor (size_t i = 1; i < pq->len; i++) {\n-\t\tsize_t parent = (i - 1) / 2;\n-\t\tcheck(pq_less(&pq->heap[parent], &pq->heap[i]));\n-\t}\n-}\n-\n-static int pq_entry_equal(struct pq_entry *a, struct pq_entry *b)\n-{\n-\tint cmp;\n-\tcheck(!reftable_record_cmp(a->rec, b->rec, &cmp));\n-\treturn !cmp && (a->index == b->index);\n-}\n-\n-static void t_pq_record(void)\n-{\n-\tstruct merged_iter_pqueue pq = { 0 };\n-\tstruct reftable_record recs[54];\n-\tsize_t N = ARRAY_SIZE(recs) - 1, i;\n-\tchar *last = NULL;\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tcheck(!reftable_record_init(&recs[i], BLOCK_TYPE_REF));\n-\t\trecs[i].u.ref.refname = xstrfmt(\"%02\"PRIuMAX, (uintmax_t)i);\n-\t}\n-\n-\ti = 1;\n-\tdo {\n-\t\tstruct pq_entry e = {\n-\t\t\t.rec = &recs[i],\n-\t\t};\n-\n-\t\tmerged_iter_pqueue_add(&pq, &e);\n-\t\tmerged_iter_pqueue_check(&pq);\n-\t\ti = (i * 7) % N;\n-\t} while (i != 1);\n-\n-\twhile (!merged_iter_pqueue_is_empty(pq)) {\n-\t\tstruct pq_entry top = merged_iter_pqueue_top(pq);\n-\t\tstruct pq_entry e;\n-\n-\t\tcheck(!merged_iter_pqueue_remove(&pq, &e));\n-\t\tmerged_iter_pqueue_check(&pq);\n-\n-\t\tcheck(pq_entry_equal(&top, &e));\n-\t\tcheck(reftable_record_type(e.rec) == BLOCK_TYPE_REF);\n-\t\tif (last)\n-\t\t\tcheck_int(strcmp(last, e.rec->u.ref.refname), <, 0);\n-\t\tlast = e.rec->u.ref.refname;\n-\t}\n-\n-\tfor (i = 0; i < N; i++)\n-\t\treftable_record_release(&recs[i]);\n-\tmerged_iter_pqueue_release(&pq);\n-}\n-\n-static void t_pq_index(void)\n-{\n-\tstruct merged_iter_pqueue pq = { 0 };\n-\tstruct reftable_record recs[13];\n-\tchar *last = NULL;\n-\tsize_t N = ARRAY_SIZE(recs), i;\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tcheck(!reftable_record_init(&recs[i], BLOCK_TYPE_REF));\n-\t\trecs[i].u.ref.refname = (char *) \"refs/heads/master\";\n-\t}\n-\n-\ti = 1;\n-\tdo {\n-\t\tstruct pq_entry e = {\n-\t\t\t.rec = &recs[i],\n-\t\t\t.index = i,\n-\t\t};\n-\n-\t\tmerged_iter_pqueue_add(&pq, &e);\n-\t\tmerged_iter_pqueue_check(&pq);\n-\t\ti = (i * 7) % N;\n-\t} while (i != 1);\n-\n-\tfor (i = N - 1; i > 0; i--) {\n-\t\tstruct pq_entry top = merged_iter_pqueue_top(pq);\n-\t\tstruct pq_entry e;\n-\n-\t\tcheck(!merged_iter_pqueue_remove(&pq, &e));\n-\t\tmerged_iter_pqueue_check(&pq);\n-\n-\t\tcheck(pq_entry_equal(&top, &e));\n-\t\tcheck(reftable_record_type(e.rec) == BLOCK_TYPE_REF);\n-\t\tcheck_int(e.index, ==, i);\n-\t\tif (last)\n-\t\t\tcheck_str(last, e.rec->u.ref.refname);\n-\t\tlast = e.rec->u.ref.refname;\n-\t}\n-\n-\tmerged_iter_pqueue_release(&pq);\n-}\n-\n-static void t_merged_iter_pqueue_top(void)\n-{\n-\tstruct merged_iter_pqueue pq = { 0 };\n-\tstruct reftable_record recs[13];\n-\tsize_t N = ARRAY_SIZE(recs), i;\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tcheck(!reftable_record_init(&recs[i], BLOCK_TYPE_REF));\n-\t\trecs[i].u.ref.refname = (char *) \"refs/heads/master\";\n-\t}\n-\n-\ti = 1;\n-\tdo {\n-\t\tstruct pq_entry e = {\n-\t\t\t.rec = &recs[i],\n-\t\t\t.index = i,\n-\t\t};\n-\n-\t\tmerged_iter_pqueue_add(&pq, &e);\n-\t\tmerged_iter_pqueue_check(&pq);\n-\t\ti = (i * 7) % N;\n-\t} while (i != 1);\n-\n-\tfor (i = N - 1; i > 0; i--) {\n-\t\tstruct pq_entry top = merged_iter_pqueue_top(pq);\n-\t\tstruct pq_entry e;\n-\n-\t\tcheck(!merged_iter_pqueue_remove(&pq, &e));\n-\n-\t\tmerged_iter_pqueue_check(&pq);\n-\t\tcheck(pq_entry_equal(&top, &e));\n-\t\tcheck(reftable_record_equal(top.rec, &recs[i], REFTABLE_HASH_SIZE_SHA1));\n-\t\tfor (size_t j = 0; i < pq.len; j++) {\n-\t\t\tcheck(pq_less(&top, &pq.heap[j]));\n-\t\t\tcheck_int(top.index, >, j);\n-\t\t}\n-\t}\n-\n-\tmerged_iter_pqueue_release(&pq);\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_pq_record(), \"pq works with record-based comparison\");\n-\tTEST(t_pq_index(), \"pq works with index-based comparison\");\n-\tTEST(t_merged_iter_pqueue_top(), \"merged_iter_pqueue_top works\");\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-pq.c b/t/unit-tests/u-reftable-pq.c\nnew file mode 100644\nindex 0000000000..ecbf08586c\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-pq.c\n@@ -0,0 +1,152 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"unit-test.h\"\n+#include \"reftable/constants.h\"\n+#include \"reftable/pq.h\"\n+#include \"strbuf.h\"\n+\n+static void merged_iter_pqueue_check(const struct merged_iter_pqueue *pq)\n+{\n+\tfor (size_t i = 1; i < pq->len; i++) {\n+\t\tsize_t parent = (i - 1) / 2;\n+\t\tcl_assert(pq_less(&pq->heap[parent], &pq->heap[i]) != 0);\n+\t}\n+}\n+\n+static int pq_entry_equal(struct pq_entry *a, struct pq_entry *b)\n+{\n+\tint cmp;\n+\tcl_assert(reftable_record_cmp(a->rec, b->rec, &cmp) == 0);\n+\treturn !cmp && (a->index == b->index);\n+}\n+\n+void test_reftable_pq__record(void)\n+{\n+\tstruct merged_iter_pqueue pq = { 0 };\n+\tstruct reftable_record recs[54];\n+\tsize_t N = ARRAY_SIZE(recs) - 1, i;\n+\tchar *last = NULL;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tcl_assert(reftable_record_init(&recs[i], BLOCK_TYPE_REF) == 0);\n+\t\trecs[i].u.ref.refname = xstrfmt(\"%02\"PRIuMAX, (uintmax_t)i);\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct pq_entry e = {\n+\t\t\t.rec = &recs[i],\n+\t\t};\n+\n+\t\tmerged_iter_pqueue_add(&pq, &e);\n+\t\tmerged_iter_pqueue_check(&pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\twhile (!merged_iter_pqueue_is_empty(pq)) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(pq);\n+\t\tstruct pq_entry e;\n+\n+\t\tcl_assert(merged_iter_pqueue_remove(&pq, &e) == 0);\n+\t\tmerged_iter_pqueue_check(&pq);\n+\n+\t\tcl_assert(pq_entry_equal(&top, &e) != 0);\n+\t\tcl_assert(reftable_record_type(e.rec) == BLOCK_TYPE_REF);\n+\t\tif (last)\n+\t\t\tcl_assert(strcmp(last, e.rec->u.ref.refname) < 0);\n+\t\tlast = e.rec->u.ref.refname;\n+\t}\n+\n+\tfor (i = 0; i < N; i++)\n+\t\treftable_record_release(&recs[i]);\n+\tmerged_iter_pqueue_release(&pq);\n+}\n+\n+void test_reftable_pq__index(void)\n+{\n+\tstruct merged_iter_pqueue pq = { 0 };\n+\tstruct reftable_record recs[13];\n+\tchar *last = NULL;\n+\tsize_t N = ARRAY_SIZE(recs), i;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tcl_assert(reftable_record_init(&recs[i], BLOCK_TYPE_REF) == 0);\n+\t\trecs[i].u.ref.refname = (char *) \"refs/heads/master\";\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct pq_entry e = {\n+\t\t\t.rec = &recs[i],\n+\t\t\t.index = i,\n+\t\t};\n+\n+\t\tmerged_iter_pqueue_add(&pq, &e);\n+\t\tmerged_iter_pqueue_check(&pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\tfor (i = N - 1; i > 0; i--) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(pq);\n+\t\tstruct pq_entry e;\n+\n+\t\tcl_assert(merged_iter_pqueue_remove(&pq, &e) == 0);\n+\t\tmerged_iter_pqueue_check(&pq);\n+\n+\t\tcl_assert(pq_entry_equal(&top, &e) != 0);\n+\t\tcl_assert(reftable_record_type(e.rec) == BLOCK_TYPE_REF);\n+\t\tcl_assert_equal_i(e.index, i);\n+\t\tif (last)\n+\t\t\tcl_assert_equal_s(last, e.rec->u.ref.refname);\n+\t\tlast = e.rec->u.ref.refname;\n+\t}\n+\n+\tmerged_iter_pqueue_release(&pq);\n+}\n+\n+void test_reftable_pq__merged_iter_pqueue_top(void)\n+{\n+\tstruct merged_iter_pqueue pq = { 0 };\n+\tstruct reftable_record recs[13];\n+\tsize_t N = ARRAY_SIZE(recs), i;\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tcl_assert(reftable_record_init(&recs[i], BLOCK_TYPE_REF) == 0);\n+\t\trecs[i].u.ref.refname = (char *) \"refs/heads/master\";\n+\t}\n+\n+\ti = 1;\n+\tdo {\n+\t\tstruct pq_entry e = {\n+\t\t\t.rec = &recs[i],\n+\t\t\t.index = i,\n+\t\t};\n+\n+\t\tmerged_iter_pqueue_add(&pq, &e);\n+\t\tmerged_iter_pqueue_check(&pq);\n+\t\ti = (i * 7) % N;\n+\t} while (i != 1);\n+\n+\tfor (i = N - 1; i > 0; i--) {\n+\t\tstruct pq_entry top = merged_iter_pqueue_top(pq);\n+\t\tstruct pq_entry e;\n+\n+\t\tcl_assert(merged_iter_pqueue_remove(&pq, &e) == 0);\n+\n+\t\tmerged_iter_pqueue_check(&pq);\n+\t\tcl_assert(pq_entry_equal(&top, &e) != 0);\n+\t\tcl_assert(reftable_record_equal(top.rec, &recs[i], REFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\tfor (size_t j = 0; i < pq.len; j++) {\n+\t\t\tcl_assert(pq_less(&top, &pq.heap[j]) != 0);\n+\t\t\tcl_assert(top.index > j);\n+\t\t}\n+\t}\n+\n+\tmerged_iter_pqueue_release(&pq);\n+}\n-- \n2.43.0\n\n"},{"id":"516861","messageId":"20250427185351.82520-7-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 6/9] t/unit-tests: convert reftable reader test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:48Z","receivedAt":"2025-04-27T18:55:00Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable reader test file to use clar by using clar assertions\nwhere necessary.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                         |  2 +-\n t/meson.build                    |  2 +-\n t/unit-tests/t-reftable-reader.c | 96 --------------------------------\n t/unit-tests/u-reftable-reader.c | 78 ++++++++++++++++++++++++++\n 4 files changed, 80 insertions(+), 98 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-reader.c\n create mode 100644 t/unit-tests/u-reftable-reader.c\n\ndiff --git a/Makefile b/Makefile\nindex 060d89df7f..948eff1c5c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1366,6 +1366,7 @@ CLAR_TEST_SUITES += u-reftable-basics\n CLAR_TEST_SUITES += u-reftable-block\n CLAR_TEST_SUITES += u-reftable-merged\n CLAR_TEST_SUITES += u-reftable-pq\n+CLAR_TEST_SUITES += u-reftable-reader\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1379,7 +1380,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-reader\n UNIT_TEST_PROGRAMS += t-reftable-readwrite\n UNIT_TEST_PROGRAMS += t-reftable-record\n UNIT_TEST_PROGRAMS += t-reftable-stack\ndiff --git a/t/meson.build b/t/meson.build\nindex 1e12bd1b96..43d52c4753 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -12,6 +12,7 @@ clar_test_suites = [\n   'unit-tests/u-reftable-block.c',\n   'unit-tests/u-reftable-merged.c',\n   'unit-tests/u-reftable-pq.c',\n+  'unit-tests/u-reftable-reader.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -59,7 +60,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-reader.c',\n   'unit-tests/t-reftable-readwrite.c',\n   'unit-tests/t-reftable-record.c',\n   'unit-tests/t-reftable-stack.c',\ndiff --git a/t/unit-tests/t-reftable-reader.c b/t/unit-tests/t-reftable-reader.c\ndeleted file mode 100644\nindex 546df6005e..0000000000\n--- a/t/unit-tests/t-reftable-reader.c\n+++ /dev/null\n@@ -1,96 +0,0 @@\n-#include \"test-lib.h\"\n-#include \"lib-reftable.h\"\n-#include \"reftable/blocksource.h\"\n-#include \"reftable/reader.h\"\n-\n-static int t_reader_seek_once(void)\n-{\n-\tstruct reftable_ref_record records[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/main\",\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 42 },\n-\t\t},\n-\t};\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_ref_record ref = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tint ret;\n-\n-\tt_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);\n-\tblock_source_from_buf(&source, &buf);\n-\n-\tret = reftable_reader_new(&reader, &source, \"name\");\n-\tcheck(!ret);\n-\n-\treftable_reader_init_ref_iterator(reader, &it);\n-\tret = reftable_iterator_seek_ref(&it, \"\");\n-\tcheck(!ret);\n-\tret = reftable_iterator_next_ref(&it, &ref);\n-\tcheck(!ret);\n-\n-\tret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1);\n-\tcheck_int(ret, ==, 1);\n-\n-\tret = reftable_iterator_next_ref(&it, &ref);\n-\tcheck_int(ret, ==, 1);\n-\n-\treftable_ref_record_release(&ref);\n-\treftable_iterator_destroy(&it);\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&buf);\n-\treturn 0;\n-}\n-\n-static int t_reader_reseek(void)\n-{\n-\tstruct reftable_ref_record records[] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/main\",\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { 42 },\n-\t\t},\n-\t};\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_ref_record ref = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tint ret;\n-\n-\tt_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);\n-\tblock_source_from_buf(&source, &buf);\n-\n-\tret = reftable_reader_new(&reader, &source, \"name\");\n-\tcheck(!ret);\n-\n-\treftable_reader_init_ref_iterator(reader, &it);\n-\n-\tfor (size_t i = 0; i < 5; i++) {\n-\t\tret = reftable_iterator_seek_ref(&it, \"\");\n-\t\tcheck(!ret);\n-\t\tret = reftable_iterator_next_ref(&it, &ref);\n-\t\tcheck(!ret);\n-\n-\t\tret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1);\n-\t\tcheck_int(ret, ==, 1);\n-\n-\t\tret = reftable_iterator_next_ref(&it, &ref);\n-\t\tcheck_int(ret, ==, 1);\n-\t}\n-\n-\treftable_ref_record_release(&ref);\n-\treftable_iterator_destroy(&it);\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&buf);\n-\treturn 0;\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_reader_seek_once(), \"reader can seek once\");\n-\tTEST(t_reader_reseek(), \"reader can reseek multiple times\");\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-reader.c b/t/unit-tests/u-reftable-reader.c\nnew file mode 100644\nindex 0000000000..6c35063105\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-reader.c\n@@ -0,0 +1,78 @@\n+#include \"unit-test.h\"\n+#include \"lib-reftable.h\"\n+#include \"reftable/blocksource.h\"\n+#include \"reftable/reader.h\"\n+\n+void test_reftable_reader__seek_once(void)\n+{\n+\tstruct reftable_ref_record records[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/main\",\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 42 },\n+\t\t},\n+\t};\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_ref_record ref = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\n+\tcl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"name\") == 0);\n+\n+\n+\treftable_reader_init_ref_iterator(reader, &it);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\tcl_assert(reftable_iterator_next_ref(&it, &ref) == 0);\n+\n+\tcl_assert_equal_i(reftable_ref_record_equal(&ref, &records[0],\n+\t\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\tcl_assert_equal_i(reftable_iterator_next_ref(&it, &ref), 1);\n+\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_reader__reseek(void)\n+{\n+\tstruct reftable_ref_record records[] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/main\",\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { 42 },\n+\t\t},\n+\t};\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_ref_record ref = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\n+\tcl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"name\") == 0);\n+\n+\treftable_reader_init_ref_iterator(reader, &it);\n+\n+\tfor (size_t i = 0; i < 5; i++) {\n+\t\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\t\tcl_assert(reftable_iterator_next_ref(&it, &ref) == 0);\n+\n+\t\tcl_assert_equal_i(reftable_ref_record_equal(&ref, &records[0],\n+\t\t\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1), 1);\n+\n+\t\tcl_assert_equal_i(reftable_iterator_next_ref(&it, &ref), 1);\n+\t}\n+\n+\treftable_ref_record_release(&ref);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&buf);\n+}\n-- \n2.43.0\n\n"},{"id":"516862","messageId":"20250427185351.82520-8-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 7/9] t/unit-tests: convert reftable readwrite test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:49Z","receivedAt":"2025-04-27T18:55:06Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable readwrite test file to use clar by using clar assertions\nwhere necessary.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                            |   2 +-\n t/meson.build                       |   2 +-\n t/unit-tests/t-reftable-readwrite.c | 985 ----------------------------\n t/unit-tests/u-reftable-readwrite.c | 870 ++++++++++++++++++++++++\n 4 files changed, 872 insertions(+), 987 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-readwrite.c\n create mode 100644 t/unit-tests/u-reftable-readwrite.c\n\ndiff --git a/Makefile b/Makefile\nindex 948eff1c5c..7511959baf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1367,6 +1367,7 @@ CLAR_TEST_SUITES += u-reftable-block\n CLAR_TEST_SUITES += u-reftable-merged\n CLAR_TEST_SUITES += u-reftable-pq\n CLAR_TEST_SUITES += u-reftable-reader\n+CLAR_TEST_SUITES += u-reftable-readwrite\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1380,7 +1381,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-readwrite\n UNIT_TEST_PROGRAMS += t-reftable-record\n UNIT_TEST_PROGRAMS += t-reftable-stack\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\ndiff --git a/t/meson.build b/t/meson.build\nindex 43d52c4753..b9e9648f7c 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -13,6 +13,7 @@ clar_test_suites = [\n   'unit-tests/u-reftable-merged.c',\n   'unit-tests/u-reftable-pq.c',\n   'unit-tests/u-reftable-reader.c',\n+  'unit-tests/u-reftable-readwrite.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -60,7 +61,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-readwrite.c',\n   'unit-tests/t-reftable-record.c',\n   'unit-tests/t-reftable-stack.c',\n ]\ndiff --git a/t/unit-tests/t-reftable-readwrite.c b/t/unit-tests/t-reftable-readwrite.c\ndeleted file mode 100644\nindex c9626831da..0000000000\n--- a/t/unit-tests/t-reftable-readwrite.c\n+++ /dev/null\n@@ -1,985 +0,0 @@\n-/*\n-Copyright 2020 Google LLC\n-\n-Use of this source code is governed by a BSD-style\n-license that can be found in the LICENSE file or at\n-https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#define DISABLE_SIGN_COMPARE_WARNINGS\n-\n-#include \"test-lib.h\"\n-#include \"lib-reftable.h\"\n-#include \"reftable/basics.h\"\n-#include \"reftable/blocksource.h\"\n-#include \"reftable/reader.h\"\n-#include \"reftable/reftable-error.h\"\n-#include \"reftable/reftable-writer.h\"\n-#include \"strbuf.h\"\n-\n-static const int update_index = 5;\n-\n-static void t_buffer(void)\n-{\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_block out = { 0 };\n-\tint n;\n-\tuint8_t in[] = \"hello\";\n-\tcheck(!reftable_buf_add(&buf, in, sizeof(in)));\n-\tblock_source_from_buf(&source, &buf);\n-\tcheck_int(block_source_size(&source), ==, 6);\n-\tn = block_source_read_block(&source, &out, 0, sizeof(in));\n-\tcheck_int(n, ==, sizeof(in));\n-\tcheck(!memcmp(in, out.data, n));\n-\treftable_block_done(&out);\n-\n-\tn = block_source_read_block(&source, &out, 1, 2);\n-\tcheck_int(n, ==, 2);\n-\tcheck(!memcmp(out.data, \"el\", 2));\n-\n-\treftable_block_done(&out);\n-\tblock_source_close(&source);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void write_table(char ***names, struct reftable_buf *buf, int N,\n-\t\t\tint block_size, enum reftable_hash hash_id)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = block_size,\n-\t\t.hash_id = hash_id,\n-\t};\n-\tstruct reftable_ref_record *refs;\n-\tstruct reftable_log_record *logs;\n-\tint i;\n-\n-\tREFTABLE_CALLOC_ARRAY(*names, N + 1);\n-\tcheck(*names != NULL);\n-\tREFTABLE_CALLOC_ARRAY(refs, N);\n-\tcheck(refs != NULL);\n-\tREFTABLE_CALLOC_ARRAY(logs, N);\n-\tcheck(logs != NULL);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\trefs[i].refname = (*names)[i] = xstrfmt(\"refs/heads/branch%02d\", i);\n-\t\trefs[i].update_index = update_index;\n-\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n-\t\tt_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tlogs[i].refname = (*names)[i];\n-\t\tlogs[i].update_index = update_index;\n-\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n-\t\tt_reftable_set_hash(logs[i].value.update.new_hash, i,\n-\t\t\t\t    REFTABLE_HASH_SHA1);\n-\t\tlogs[i].value.update.message = (char *) \"message\";\n-\t}\n-\n-\tt_reftable_write_to_buf(buf, refs, N, logs, N, &opts);\n-\n-\treftable_free(refs);\n-\treftable_free(logs);\n-}\n-\n-static void t_log_buffer_size(void)\n-{\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 4096,\n-\t};\n-\tint err;\n-\tint i;\n-\tstruct reftable_log_record\n-\t\tlog = { .refname = (char *) \"refs/heads/master\",\n-\t\t\t.update_index = update_index,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value = { .update = {\n-\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n-\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n-\t\t\t\t\t   .tz_offset = 100,\n-\t\t\t\t\t   .time = 0x5e430672,\n-\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n-\t\t\t\t   } } };\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\n-\t/* This tests buffer extension for log compression. Must use a random\n-\t   hash, to ensure that the compressed part is larger than the original.\n-\t*/\n-\tfor (i = 0; i < REFTABLE_HASH_SIZE_SHA1; i++) {\n-\t\tlog.value.update.old_hash[i] = (uint8_t)(git_rand(0) % 256);\n-\t\tlog.value.update.new_hash[i] = (uint8_t)(git_rand(0) % 256);\n-\t}\n-\treftable_writer_set_limits(w, update_index, update_index);\n-\terr = reftable_writer_add_log(w, &log);\n-\tcheck(!err);\n-\terr = reftable_writer_close(w);\n-\tcheck(!err);\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_log_overflow(void)\n-{\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tchar msg[256] = { 0 };\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = ARRAY_SIZE(msg),\n-\t};\n-\tint err;\n-\tstruct reftable_log_record log = {\n-\t\t.refname = (char *) \"refs/heads/master\",\n-\t\t.update_index = update_index,\n-\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t.value = {\n-\t\t\t.update = {\n-\t\t\t\t.old_hash = { 1 },\n-\t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n-\t\t\t\t.email = (char *) \"hanwen@google.com\",\n-\t\t\t\t.tz_offset = 100,\n-\t\t\t\t.time = 0x5e430672,\n-\t\t\t\t.message = msg,\n-\t\t\t},\n-\t\t},\n-\t};\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\n-\tmemset(msg, 'x', sizeof(msg) - 1);\n-\treftable_writer_set_limits(w, update_index, update_index);\n-\terr = reftable_writer_add_log(w, &log);\n-\tcheck_int(err, ==, REFTABLE_ENTRY_TOO_BIG_ERROR);\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_log_write_limits(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_log_record log = {\n-\t\t.refname = (char *)\"refs/head/master\",\n-\t\t.update_index = 0,\n-\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t.value = {\n-\t\t\t.update = {\n-\t\t\t\t.old_hash = { 1 },\n-\t\t\t\t.new_hash = { 2 },\n-\t\t\t\t.name = (char *)\"Han-Wen Nienhuys\",\n-\t\t\t\t.email = (char *)\"hanwen@google.com\",\n-\t\t\t\t.tz_offset = 100,\n-\t\t\t\t.time = 0x5e430672,\n-\t\t\t},\n-\t\t},\n-\t};\n-\tint err;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\n-\t/* write with update_index (0) below set limits (1, 1) */\n-\terr = reftable_writer_add_log(w, &log);\n-\tcheck_int(err, ==, 0);\n-\n-\t/* write with update_index (1) in the set limits (1, 1) */\n-\tlog.update_index = 1;\n-\terr = reftable_writer_add_log(w, &log);\n-\tcheck_int(err, ==, 0);\n-\n-\t/* write with update_index (3) above set limits (1, 1) */\n-\tlog.update_index = 3;\n-\terr = reftable_writer_add_log(w, &log);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_log_write_read(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 256,\n-\t};\n-\tstruct reftable_ref_record ref = { 0 };\n-\tstruct reftable_log_record log = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tconst struct reftable_stats *stats = NULL;\n-\tint N = 2, err, i, n;\n-\tchar **names;\n-\n-\tnames = reftable_calloc(N + 1, sizeof(*names));\n-\tcheck(names != NULL);\n-\n-\treftable_writer_set_limits(w, 0, N);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tchar name[256];\n-\t\tstruct reftable_ref_record ref = { 0 };\n-\t\tsnprintf(name, sizeof(name), \"b%02d%0*d\", i, 130, 7);\n-\t\tnames[i] = xstrdup(name);\n-\t\tref.refname = name;\n-\t\tref.update_index = i;\n-\n-\t\terr = reftable_writer_add_ref(w, &ref);\n-\t\tcheck(!err);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tstruct reftable_log_record log = { 0 };\n-\n-\t\tlog.refname = names[i];\n-\t\tlog.update_index = i;\n-\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n-\t\tt_reftable_set_hash(log.value.update.old_hash, i,\n-\t\t\t\t    REFTABLE_HASH_SHA1);\n-\t\tt_reftable_set_hash(log.value.update.new_hash, i + 1,\n-\t\t\t\t    REFTABLE_HASH_SHA1);\n-\n-\t\terr = reftable_writer_add_log(w, &log);\n-\t\tcheck(!err);\n-\t}\n-\n-\tn = reftable_writer_close(w);\n-\tcheck_int(n, ==, 0);\n-\n-\tstats = reftable_writer_stats(w);\n-\tcheck_int(stats->log_stats.blocks, >, 0);\n-\treftable_writer_free(w);\n-\tw = NULL;\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&reader, &source, \"file.log\");\n-\tcheck(!err);\n-\n-\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\tcheck(!err);\n-\n-\terr = reftable_iterator_seek_ref(&it, names[N - 1]);\n-\tcheck(!err);\n-\n-\terr = reftable_iterator_next_ref(&it, &ref);\n-\tcheck(!err);\n-\n-\t/* end of iteration. */\n-\terr = reftable_iterator_next_ref(&it, &ref);\n-\tcheck_int(err, >, 0);\n-\n-\treftable_iterator_destroy(&it);\n-\treftable_ref_record_release(&ref);\n-\n-\terr = reftable_reader_init_log_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_log(&it, \"\");\n-\tcheck(!err);\n-\n-\tfor (i = 0; ; i++) {\n-\t\tint err = reftable_iterator_next_log(&it, &log);\n-\t\tif (err > 0)\n-\t\t\tbreak;\n-\t\tcheck(!err);\n-\t\tcheck_str(names[i], log.refname);\n-\t\tcheck_int(i, ==, log.update_index);\n-\t\treftable_log_record_release(&log);\n-\t}\n-\n-\tcheck_int(i, ==, N);\n-\treftable_iterator_destroy(&it);\n-\n-\t/* cleanup. */\n-\treftable_buf_release(&buf);\n-\tfree_names(names);\n-\treftable_reader_decref(reader);\n-}\n-\n-static void t_log_zlib_corruption(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 256,\n-\t};\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tconst struct reftable_stats *stats = NULL;\n-\tchar message[100] = { 0 };\n-\tint err, i, n;\n-\tstruct reftable_log_record log = {\n-\t\t.refname = (char *) \"refname\",\n-\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t.value = {\n-\t\t\t.update = {\n-\t\t\t\t.new_hash = { 1 },\n-\t\t\t\t.old_hash = { 2 },\n-\t\t\t\t.name = (char *) \"My Name\",\n-\t\t\t\t.email = (char *) \"myname@invalid\",\n-\t\t\t\t.message = message,\n-\t\t\t},\n-\t\t},\n-\t};\n-\n-\tfor (i = 0; i < sizeof(message) - 1; i++)\n-\t\tmessage[i] = (uint8_t)(git_rand(0) % 64 + ' ');\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\n-\terr = reftable_writer_add_log(w, &log);\n-\tcheck(!err);\n-\n-\tn = reftable_writer_close(w);\n-\tcheck_int(n, ==, 0);\n-\n-\tstats = reftable_writer_stats(w);\n-\tcheck_int(stats->log_stats.blocks, >, 0);\n-\treftable_writer_free(w);\n-\tw = NULL;\n-\n-\t/* corrupt the data. */\n-\tbuf.buf[50] ^= 0x99;\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&reader, &source, \"file.log\");\n-\tcheck(!err);\n-\n-\terr = reftable_reader_init_log_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_log(&it, \"refname\");\n-\tcheck_int(err, ==, REFTABLE_ZLIB_ERROR);\n-\n-\treftable_iterator_destroy(&it);\n-\n-\t/* cleanup. */\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_table_read_write_sequential(void)\n-{\n-\tchar **names;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tint N = 50;\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_reader *reader;\n-\tint err = 0;\n-\tint j = 0;\n-\n-\twrite_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&reader, &source, \"file.ref\");\n-\tcheck(!err);\n-\n-\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, \"\");\n-\tcheck(!err);\n-\n-\tfor (j = 0; ; j++) {\n-\t\tstruct reftable_ref_record ref = { 0 };\n-\t\tint r = reftable_iterator_next_ref(&it, &ref);\n-\t\tcheck_int(r, >=, 0);\n-\t\tif (r > 0)\n-\t\t\tbreak;\n-\t\tcheck_str(names[j], ref.refname);\n-\t\tcheck_int(update_index, ==, ref.update_index);\n-\t\treftable_ref_record_release(&ref);\n-\t}\n-\tcheck_int(j, ==, N);\n-\n-\treftable_iterator_destroy(&it);\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&buf);\n-\tfree_names(names);\n-}\n-\n-static void t_table_write_small_table(void)\n-{\n-\tchar **names;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tint N = 1;\n-\twrite_table(&names, &buf, N, 4096, REFTABLE_HASH_SHA1);\n-\tcheck_int(buf.len, <, 200);\n-\treftable_buf_release(&buf);\n-\tfree_names(names);\n-}\n-\n-static void t_table_read_api(void)\n-{\n-\tchar **names;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tint N = 50;\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_block_source source = { 0 };\n-\tint err;\n-\tstruct reftable_log_record log = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\n-\twrite_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&reader, &source, \"file.ref\");\n-\tcheck(!err);\n-\n-\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, names[0]);\n-\tcheck(!err);\n-\n-\terr = reftable_iterator_next_log(&it, &log);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\treftable_buf_release(&buf);\n-\tfree_names(names);\n-\treftable_iterator_destroy(&it);\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_table_read_write_seek(int index, enum reftable_hash hash_id)\n-{\n-\tchar **names;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tint N = 50;\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_block_source source = { 0 };\n-\tint err;\n-\tint i = 0;\n-\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_buf pastLast = REFTABLE_BUF_INIT;\n-\tstruct reftable_ref_record ref = { 0 };\n-\n-\twrite_table(&names, &buf, N, 256, hash_id);\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&reader, &source, \"file.ref\");\n-\tcheck(!err);\n-\tcheck_int(hash_id, ==, reftable_reader_hash_id(reader));\n-\n-\tif (!index) {\n-\t\treader->ref_offsets.index_offset = 0;\n-\t} else {\n-\t\tcheck_int(reader->ref_offsets.index_offset, >, 0);\n-\t}\n-\n-\tfor (i = 1; i < N; i++) {\n-\t\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\t\tcheck(!err);\n-\t\terr = reftable_iterator_seek_ref(&it, names[i]);\n-\t\tcheck(!err);\n-\t\terr = reftable_iterator_next_ref(&it, &ref);\n-\t\tcheck(!err);\n-\t\tcheck_str(names[i], ref.refname);\n-\t\tcheck_int(REFTABLE_REF_VAL1, ==, ref.value_type);\n-\t\tcheck_int(i, ==, ref.value.val1[0]);\n-\n-\t\treftable_ref_record_release(&ref);\n-\t\treftable_iterator_destroy(&it);\n-\t}\n-\n-\tcheck(!reftable_buf_addstr(&pastLast, names[N - 1]));\n-\tcheck(!reftable_buf_addstr(&pastLast, \"/\"));\n-\n-\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, pastLast.buf);\n-\tif (err == 0) {\n-\t\tstruct reftable_ref_record ref = { 0 };\n-\t\tint err = reftable_iterator_next_ref(&it, &ref);\n-\t\tcheck_int(err, >, 0);\n-\t} else {\n-\t\tcheck_int(err, >, 0);\n-\t}\n-\n-\treftable_buf_release(&pastLast);\n-\treftable_iterator_destroy(&it);\n-\n-\treftable_buf_release(&buf);\n-\tfree_names(names);\n-\treftable_reader_decref(reader);\n-}\n-\n-static void t_table_read_write_seek_linear(void)\n-{\n-\tt_table_read_write_seek(0, REFTABLE_HASH_SHA1);\n-}\n-\n-static void t_table_read_write_seek_linear_sha256(void)\n-{\n-\tt_table_read_write_seek(0, REFTABLE_HASH_SHA256);\n-}\n-\n-static void t_table_read_write_seek_index(void)\n-{\n-\tt_table_read_write_seek(1, REFTABLE_HASH_SHA1);\n-}\n-\n-static void t_table_refs_for(int indexed)\n-{\n-\tchar **want_names;\n-\tint want_names_len = 0;\n-\tuint8_t want_hash[REFTABLE_HASH_SIZE_SHA1];\n-\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 256,\n-\t};\n-\tstruct reftable_ref_record ref = { 0 };\n-\tstruct reftable_reader *reader;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_iterator it = { 0 };\n-\tint N = 50, n, j, err, i;\n-\n-\twant_names = reftable_calloc(N + 1, sizeof(*want_names));\n-\tcheck(want_names != NULL);\n-\n-\tt_reftable_set_hash(want_hash, 4, REFTABLE_HASH_SHA1);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tuint8_t hash[REFTABLE_HASH_SIZE_SHA1];\n-\t\tchar fill[51] = { 0 };\n-\t\tchar name[100];\n-\t\tstruct reftable_ref_record ref = { 0 };\n-\n-\t\tmemset(hash, i, sizeof(hash));\n-\t\tmemset(fill, 'x', 50);\n-\t\t/* Put the variable part in the start */\n-\t\tsnprintf(name, sizeof(name), \"br%02d%s\", i, fill);\n-\t\tname[40] = 0;\n-\t\tref.refname = name;\n-\n-\t\tref.value_type = REFTABLE_REF_VAL2;\n-\t\tt_reftable_set_hash(ref.value.val2.value, i / 4,\n-\t\t\t\t    REFTABLE_HASH_SHA1);\n-\t\tt_reftable_set_hash(ref.value.val2.target_value, 3 + i / 4,\n-\t\t\t\t    REFTABLE_HASH_SHA1);\n-\n-\t\t/* 80 bytes / entry, so 3 entries per block. Yields 17\n-\t\t */\n-\t\t/* blocks. */\n-\t\tn = reftable_writer_add_ref(w, &ref);\n-\t\tcheck_int(n, ==, 0);\n-\n-\t\tif (!memcmp(ref.value.val2.value, want_hash, REFTABLE_HASH_SIZE_SHA1) ||\n-\t\t    !memcmp(ref.value.val2.target_value, want_hash, REFTABLE_HASH_SIZE_SHA1))\n-\t\t\twant_names[want_names_len++] = xstrdup(name);\n-\t}\n-\n-\tn = reftable_writer_close(w);\n-\tcheck_int(n, ==, 0);\n-\n-\treftable_writer_free(w);\n-\tw = NULL;\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&reader, &source, \"file.ref\");\n-\tcheck(!err);\n-\tif (!indexed)\n-\t\treader->obj_offsets.is_present = 0;\n-\n-\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, \"\");\n-\tcheck(!err);\n-\treftable_iterator_destroy(&it);\n-\n-\terr = reftable_reader_refs_for(reader, &it, want_hash);\n-\tcheck(!err);\n-\n-\tfor (j = 0; ; j++) {\n-\t\tint err = reftable_iterator_next_ref(&it, &ref);\n-\t\tcheck_int(err, >=, 0);\n-\t\tif (err > 0)\n-\t\t\tbreak;\n-\t\tcheck_int(j, <, want_names_len);\n-\t\tcheck_str(ref.refname, want_names[j]);\n-\t\treftable_ref_record_release(&ref);\n-\t}\n-\tcheck_int(j, ==, want_names_len);\n-\n-\treftable_buf_release(&buf);\n-\tfree_names(want_names);\n-\treftable_iterator_destroy(&it);\n-\treftable_reader_decref(reader);\n-}\n-\n-static void t_table_refs_for_no_index(void)\n-{\n-\tt_table_refs_for(0);\n-}\n-\n-static void t_table_refs_for_obj_index(void)\n-{\n-\tt_table_refs_for(1);\n-}\n-\n-static void t_write_empty_table(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_reader *rd = NULL;\n-\tstruct reftable_ref_record rec = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tint err;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\n-\terr = reftable_writer_close(w);\n-\tcheck_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR);\n-\treftable_writer_free(w);\n-\n-\tcheck_uint(buf.len, ==, header_size(1) + footer_size(1));\n-\n-\tblock_source_from_buf(&source, &buf);\n-\n-\terr = reftable_reader_new(&rd, &source, \"filename\");\n-\tcheck(!err);\n-\n-\terr = reftable_reader_init_ref_iterator(rd, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, \"\");\n-\tcheck(!err);\n-\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck_int(err, >, 0);\n-\n-\treftable_iterator_destroy(&it);\n-\treftable_reader_decref(rd);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_write_object_id_min_length(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 75,\n-\t};\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_ref_record ref = {\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t.value.val1 = {42},\n-\t};\n-\tint err;\n-\tint i;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\n-\t/* Write the same hash in many refs. If there is only 1 hash, the\n-\t * disambiguating prefix is length 0 */\n-\tfor (i = 0; i < 256; i++) {\n-\t\tchar name[256];\n-\t\tsnprintf(name, sizeof(name), \"ref%05d\", i);\n-\t\tref.refname = name;\n-\t\terr = reftable_writer_add_ref(w, &ref);\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_writer_close(w);\n-\tcheck(!err);\n-\tcheck_int(reftable_writer_stats(w)->object_id_len, ==, 2);\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_write_object_id_length(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 75,\n-\t};\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_ref_record ref = {\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t.value.val1 = {42},\n-\t};\n-\tint err;\n-\tint i;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\n-\t/* Write the same hash in many refs. If there is only 1 hash, the\n-\t * disambiguating prefix is length 0 */\n-\tfor (i = 0; i < 256; i++) {\n-\t\tchar name[256];\n-\t\tsnprintf(name, sizeof(name), \"ref%05d\", i);\n-\t\tref.refname = name;\n-\t\tref.value.val1[15] = i;\n-\t\terr = reftable_writer_add_ref(w, &ref);\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_writer_close(w);\n-\tcheck(!err);\n-\tcheck_int(reftable_writer_stats(w)->object_id_len, ==, 16);\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_write_empty_key(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *) \"\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_DELETION,\n-\t};\n-\tint err;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\terr = reftable_writer_add_ref(w, &ref);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\terr = reftable_writer_close(w);\n-\tcheck_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR);\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_write_key_order(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);\n-\tstruct reftable_ref_record refs[2] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"b\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value = {\n-\t\t\t\t.symref = (char *) \"target\",\n-\t\t\t},\n-\t\t}, {\n-\t\t\t.refname = (char *) \"a\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value = {\n-\t\t\t\t.symref = (char *) \"target\",\n-\t\t\t},\n-\t\t}\n-\t};\n-\tint err;\n-\n-\treftable_writer_set_limits(w, 1, 1);\n-\terr = reftable_writer_add_ref(w, &refs[0]);\n-\tcheck(!err);\n-\terr = reftable_writer_add_ref(w, &refs[1]);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\trefs[0].update_index = 2;\n-\terr = reftable_writer_add_ref(w, &refs[0]);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\treftable_writer_close(w);\n-\treftable_writer_free(w);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_write_multiple_indices(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 100,\n-\t};\n-\tstruct reftable_buf writer_buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tconst struct reftable_stats *stats;\n-\tstruct reftable_writer *writer;\n-\tstruct reftable_reader *reader;\n-\tchar buf[128];\n-\tint err, i;\n-\n-\twriter = t_reftable_strbuf_writer(&writer_buf, &opts);\n-\treftable_writer_set_limits(writer, 1, 1);\n-\tfor (i = 0; i < 100; i++) {\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = {i},\n-\t\t};\n-\n-\t\tsnprintf(buf, sizeof(buf), \"refs/heads/%04d\", i);\n-\t\tref.refname = buf;\n-\n-\t\terr = reftable_writer_add_ref(writer, &ref);\n-\t\tcheck(!err);\n-\t}\n-\n-\tfor (i = 0; i < 100; i++) {\n-\t\tstruct reftable_log_record log = {\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value.update = {\n-\t\t\t\t.old_hash = { i },\n-\t\t\t\t.new_hash = { i },\n-\t\t\t},\n-\t\t};\n-\n-\t\tsnprintf(buf, sizeof(buf), \"refs/heads/%04d\", i);\n-\t\tlog.refname = buf;\n-\n-\t\terr = reftable_writer_add_log(writer, &log);\n-\t\tcheck(!err);\n-\t}\n-\n-\treftable_writer_close(writer);\n-\n-\t/*\n-\t * The written data should be sufficiently large to result in indices\n-\t * for each of the block types.\n-\t */\n-\tstats = reftable_writer_stats(writer);\n-\tcheck_int(stats->ref_stats.index_offset, >, 0);\n-\tcheck_int(stats->obj_stats.index_offset, >, 0);\n-\tcheck_int(stats->log_stats.index_offset, >, 0);\n-\n-\tblock_source_from_buf(&source, &writer_buf);\n-\terr = reftable_reader_new(&reader, &source, \"filename\");\n-\tcheck(!err);\n-\n-\t/*\n-\t * Seeking the log uses the log index now. In case there is any\n-\t * confusion regarding indices we would notice here.\n-\t */\n-\terr = reftable_reader_init_log_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_log(&it, \"\");\n-\tcheck(!err);\n-\n-\treftable_iterator_destroy(&it);\n-\treftable_writer_free(writer);\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&writer_buf);\n-}\n-\n-static void t_write_multi_level_index(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.block_size = 100,\n-\t};\n-\tstruct reftable_buf writer_buf = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tconst struct reftable_stats *stats;\n-\tstruct reftable_writer *writer;\n-\tstruct reftable_reader *reader;\n-\tint err;\n-\n-\twriter = t_reftable_strbuf_writer(&writer_buf, &opts);\n-\treftable_writer_set_limits(writer, 1, 1);\n-\tfor (size_t i = 0; i < 200; i++) {\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = {i},\n-\t\t};\n-\t\tchar buf[128];\n-\n-\t\tsnprintf(buf, sizeof(buf), \"refs/heads/%03\" PRIuMAX, (uintmax_t)i);\n-\t\tref.refname = buf;\n-\n-\t\terr = reftable_writer_add_ref(writer, &ref);\n-\t\tcheck(!err);\n-\t}\n-\treftable_writer_close(writer);\n-\n-\t/*\n-\t * The written refs should be sufficiently large to result in a\n-\t * multi-level index.\n-\t */\n-\tstats = reftable_writer_stats(writer);\n-\tcheck_int(stats->ref_stats.max_index_level, ==, 2);\n-\n-\tblock_source_from_buf(&source, &writer_buf);\n-\terr = reftable_reader_new(&reader, &source, \"filename\");\n-\tcheck(!err);\n-\n-\t/*\n-\t * Seeking the last ref should work as expected.\n-\t */\n-\terr = reftable_reader_init_ref_iterator(reader, &it);\n-\tcheck(!err);\n-\terr = reftable_iterator_seek_ref(&it, \"refs/heads/199\");\n-\tcheck(!err);\n-\n-\treftable_iterator_destroy(&it);\n-\treftable_writer_free(writer);\n-\treftable_reader_decref(reader);\n-\treftable_buf_release(&writer_buf);\n-\treftable_buf_release(&buf);\n-}\n-\n-static void t_corrupt_table_empty(void)\n-{\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_reader *reader;\n-\tint err;\n-\n-\tblock_source_from_buf(&source, &buf);\n-\terr = reftable_reader_new(&reader, &source, \"file.log\");\n-\tcheck_int(err, ==, REFTABLE_FORMAT_ERROR);\n-}\n-\n-static void t_corrupt_table(void)\n-{\n-\tuint8_t zeros[1024] = { 0 };\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tstruct reftable_block_source source = { 0 };\n-\tstruct reftable_reader *reader;\n-\tint err;\n-\tcheck(!reftable_buf_add(&buf, zeros, sizeof(zeros)));\n-\n-\tblock_source_from_buf(&source, &buf);\n-\terr = reftable_reader_new(&reader, &source, \"file.log\");\n-\tcheck_int(err, ==, REFTABLE_FORMAT_ERROR);\n-\n-\treftable_buf_release(&buf);\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_buffer(), \"strbuf works as blocksource\");\n-\tTEST(t_corrupt_table(), \"read-write on corrupted table\");\n-\tTEST(t_corrupt_table_empty(), \"read-write on an empty table\");\n-\tTEST(t_log_buffer_size(), \"buffer extension for log compression\");\n-\tTEST(t_log_overflow(), \"log overflow returns expected error\");\n-\tTEST(t_log_write_limits(), \"writer limits for writing log records\");\n-\tTEST(t_log_write_read(), \"read-write on log records\");\n-\tTEST(t_log_zlib_corruption(), \"reading corrupted log record returns expected error\");\n-\tTEST(t_table_read_api(), \"read on a table\");\n-\tTEST(t_table_read_write_seek_index(), \"read-write on a table with index\");\n-\tTEST(t_table_read_write_seek_linear(), \"read-write on a table without index (SHA1)\");\n-\tTEST(t_table_read_write_seek_linear_sha256(), \"read-write on a table without index (SHA256)\");\n-\tTEST(t_table_read_write_sequential(), \"sequential read-write on a table\");\n-\tTEST(t_table_refs_for_no_index(), \"refs-only table with no index\");\n-\tTEST(t_table_refs_for_obj_index(), \"refs-only table with index\");\n-\tTEST(t_table_write_small_table(), \"write_table works\");\n-\tTEST(t_write_empty_key(), \"write on refs with empty keys\");\n-\tTEST(t_write_empty_table(), \"read-write on empty tables\");\n-\tTEST(t_write_key_order(), \"refs must be written in increasing order\");\n-\tTEST(t_write_multi_level_index(), \"table with multi-level index\");\n-\tTEST(t_write_multiple_indices(), \"table with indices for multiple block types\");\n-\tTEST(t_write_object_id_length(), \"prefix compression on writing refs\");\n-\tTEST(t_write_object_id_min_length(), \"prefix compression on writing refs\");\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c\nnew file mode 100644\nindex 0000000000..3d6bdcfceb\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-readwrite.c\n@@ -0,0 +1,870 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#define DISABLE_SIGN_COMPARE_WARNINGS\n+\n+#include \"unit-test.h\"\n+#include \"lib-reftable.h\"\n+#include \"reftable/basics.h\"\n+#include \"reftable/blocksource.h\"\n+#include \"reftable/reader.h\"\n+#include \"reftable/reftable-error.h\"\n+#include \"reftable/reftable-writer.h\"\n+#include \"strbuf.h\"\n+\n+static const int update_index = 5;\n+\n+void test_reftable_readwrite__buffer(void)\n+{\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_block out = { 0 };\n+\tint n;\n+\tuint8_t in[] = \"hello\";\n+\tcl_assert(reftable_buf_add(&buf, in, sizeof(in)) == 0);\n+\tblock_source_from_buf(&source, &buf);\n+\tcl_assert_equal_i(block_source_size(&source), 6);\n+\tn = block_source_read_block(&source, &out, 0, sizeof(in));\n+\tcl_assert_equal_i(n, sizeof(in));\n+\tcl_assert(memcmp(in, out.data, n) == 0);\n+\treftable_block_done(&out);\n+\n+\tcl_assert_equal_i(block_source_read_block(&source, &out, 1, 2), 2);\n+\tcl_assert(memcmp(out.data, \"el\", 2) == 0);\n+\n+\treftable_block_done(&out);\n+\tblock_source_close(&source);\n+\treftable_buf_release(&buf);\n+}\n+\n+static void write_table(char ***names, struct reftable_buf *buf, int N,\n+\t\t\tint block_size, enum reftable_hash hash_id)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = block_size,\n+\t\t.hash_id = hash_id,\n+\t};\n+\tstruct reftable_ref_record *refs;\n+\tstruct reftable_log_record *logs;\n+\tint i;\n+\n+\tREFTABLE_CALLOC_ARRAY(*names, N + 1);\n+\tcl_assert(*names != NULL);\n+\tREFTABLE_CALLOC_ARRAY(refs, N);\n+\tcl_assert(refs != NULL);\n+\tREFTABLE_CALLOC_ARRAY(logs, N);\n+\tcl_assert(logs != NULL);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\trefs[i].refname = (*names)[i] = xstrfmt(\"refs/heads/branch%02d\", i);\n+\t\trefs[i].update_index = update_index;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\tcl_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tlogs[i].refname = (*names)[i];\n+\t\tlogs[i].update_index = update_index;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tcl_reftable_set_hash(logs[i].value.update.new_hash, i,\n+\t\t\t\t    REFTABLE_HASH_SHA1);\n+\t\tlogs[i].value.update.message = (char *) \"message\";\n+\t}\n+\n+\tcl_reftable_write_to_buf(buf, refs, N, logs, N, &opts);\n+\n+\treftable_free(refs);\n+\treftable_free(logs);\n+}\n+\n+void test_reftable_readwrite__log_buffer_size(void)\n+{\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 4096,\n+\t};\n+\tint i;\n+\tstruct reftable_log_record\n+\t\tlog = { .refname = (char *) \"refs/heads/master\",\n+\t\t\t.update_index = update_index,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = { .update = {\n+\t\t\t\t\t   .name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t\t   .email = (char *) \"hanwen@google.com\",\n+\t\t\t\t\t   .tz_offset = 100,\n+\t\t\t\t\t   .time = 0x5e430672,\n+\t\t\t\t\t   .message = (char *) \"commit: 9\\n\",\n+\t\t\t\t   } } };\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\n+\t/* This tests buffer extension for log compression. Must use a random\n+\t   hash, to ensure that the compressed part is larger than the original.\n+\t*/\n+\tfor (i = 0; i < REFTABLE_HASH_SIZE_SHA1; i++) {\n+\t\tlog.value.update.old_hash[i] = (uint8_t)(git_rand(0) % 256);\n+\t\tlog.value.update.new_hash[i] = (uint8_t)(git_rand(0) % 256);\n+\t}\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\tcl_assert(reftable_writer_add_log(w, &log) == 0);\n+\tcl_assert(reftable_writer_close(w) == 0);\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__log_overflow(void)\n+{\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tchar msg[256] = { 0 };\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = ARRAY_SIZE(msg),\n+\t};\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *) \"refs/heads/master\",\n+\t\t.update_index = update_index,\n+\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t.value = {\n+\t\t\t.update = {\n+\t\t\t\t.old_hash = { 1 },\n+\t\t\t\t.new_hash = { 2 },\n+\t\t\t\t.name = (char *) \"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *) \"hanwen@google.com\",\n+\t\t\t\t.tz_offset = 100,\n+\t\t\t\t.time = 0x5e430672,\n+\t\t\t\t.message = msg,\n+\t\t\t},\n+\t\t},\n+\t};\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\n+\tmemset(msg, 'x', sizeof(msg) - 1);\n+\treftable_writer_set_limits(w, update_index, update_index);\n+\tcl_assert_equal_i(reftable_writer_add_log(w, &log), REFTABLE_ENTRY_TOO_BIG_ERROR);\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__log_write_limits(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *)\"refs/head/master\",\n+\t\t.update_index = 0,\n+\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t.value = {\n+\t\t\t.update = {\n+\t\t\t\t.old_hash = { 1 },\n+\t\t\t\t.new_hash = { 2 },\n+\t\t\t\t.name = (char *)\"Han-Wen Nienhuys\",\n+\t\t\t\t.email = (char *)\"hanwen@google.com\",\n+\t\t\t\t.tz_offset = 100,\n+\t\t\t\t.time = 0x5e430672,\n+\t\t\t},\n+\t\t},\n+\t};\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\t/* write with update_index (0) below set limits (1, 1) */\n+\tcl_assert_equal_i(reftable_writer_add_log(w, &log), 0);\n+\n+\t/* write with update_index (1) in the set limits (1, 1) */\n+\tlog.update_index = 1;\n+\tcl_assert_equal_i(reftable_writer_add_log(w, &log), 0);\n+\n+\t/* write with update_index (3) above set limits (1, 1) */\n+\tlog.update_index = 3;\n+\tcl_assert_equal_i(reftable_writer_add_log(w, &log), REFTABLE_API_ERROR);\n+\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__log_write_read(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { 0 };\n+\tstruct reftable_log_record log = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tconst struct reftable_stats *stats = NULL;\n+\tint N = 2, i;\n+\tchar **names;\n+\n+\tnames = reftable_calloc(N + 1, sizeof(*names));\n+\tcl_assert(names != NULL);\n+\n+\treftable_writer_set_limits(w, 0, N);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[256];\n+\t\tstruct reftable_ref_record ref = { 0 };\n+\t\tsnprintf(name, sizeof(name), \"b%02d%0*d\", i, 130, 7);\n+\t\tnames[i] = xstrdup(name);\n+\t\tref.refname = name;\n+\t\tref.update_index = i;\n+\n+\t\tcl_assert(reftable_writer_add_ref(w, &ref) == 0);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_log_record log = { 0 };\n+\n+\t\tlog.refname = names[i];\n+\t\tlog.update_index = i;\n+\t\tlog.value_type = REFTABLE_LOG_UPDATE;\n+\t\tcl_reftable_set_hash(log.value.update.old_hash, i,\n+\t\t\t\t    REFTABLE_HASH_SHA1);\n+\t\tcl_reftable_set_hash(log.value.update.new_hash, i + 1,\n+\t\t\t\t    REFTABLE_HASH_SHA1);\n+\n+\t\tcl_assert(reftable_writer_add_log(w, &log) == 0);\n+\t}\n+\n+\tcl_assert_equal_i(reftable_writer_close(w), 0);\n+\n+\tstats = reftable_writer_stats(w);\n+\tcl_assert(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"file.log\") == 0);\n+\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_ref(&it, names[N - 1]) == 0);\n+\tcl_assert(reftable_iterator_next_ref(&it, &ref) == 0);\n+\n+\t/* end of iteration. */\n+\tcl_assert(reftable_iterator_next_ref(&it, &ref) > 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_ref_record_release(&ref);\n+\n+\tcl_assert(reftable_reader_init_log_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_log(&it, \"\") == 0);\n+\n+\tfor (i = 0; ; i++) {\n+\t\tint err = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0)\n+\t\t\tbreak;\n+\t\tcl_assert(err == 0);\n+\t\tcl_assert_equal_s(names[i], log.refname);\n+\t\tcl_assert_equal_i(i, log.update_index);\n+\t\treftable_log_record_release(&log);\n+\t}\n+\n+\tcl_assert_equal_i(i, N);\n+\treftable_iterator_destroy(&it);\n+\n+\t/* cleanup. */\n+\treftable_buf_release(&buf);\n+\tfree_names(names);\n+\treftable_reader_decref(reader);\n+}\n+\n+void test_reftable_readwrite__log_zlib_corruption(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tconst struct reftable_stats *stats = NULL;\n+\tchar message[100] = { 0 };\n+\tint i;\n+\tstruct reftable_log_record log = {\n+\t\t.refname = (char *) \"refname\",\n+\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t.value = {\n+\t\t\t.update = {\n+\t\t\t\t.new_hash = { 1 },\n+\t\t\t\t.old_hash = { 2 },\n+\t\t\t\t.name = (char *) \"My Name\",\n+\t\t\t\t.email = (char *) \"myname@invalid\",\n+\t\t\t\t.message = message,\n+\t\t\t},\n+\t\t},\n+\t};\n+\n+\tfor (i = 0; i < sizeof(message) - 1; i++)\n+\t\tmessage[i] = (uint8_t)(git_rand(0) % 64 + ' ');\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\tcl_assert(reftable_writer_add_log(w, &log) == 0);\n+\tcl_assert_equal_i(reftable_writer_close(w), 0);\n+\n+\tstats = reftable_writer_stats(w);\n+\tcl_assert(stats->log_stats.blocks > 0);\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\t/* corrupt the data. */\n+\tbuf.buf[50] ^= 0x99;\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"file.log\") == 0);\n+\tcl_assert(reftable_reader_init_log_iterator(reader, &it) == 0);\n+\tcl_assert_equal_i(reftable_iterator_seek_log(&it, \"refname\"), REFTABLE_ZLIB_ERROR);\n+\n+\treftable_iterator_destroy(&it);\n+\n+\t/* cleanup. */\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__table_read_write_sequential(void)\n+{\n+\tchar **names;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_reader *reader;\n+\tint j = 0;\n+\n+\twrite_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"file.ref\") == 0);\n+\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\n+\tfor (j = 0; ; j++) {\n+\t\tstruct reftable_ref_record ref = { 0 };\n+\t\tint r = reftable_iterator_next_ref(&it, &ref);\n+\t\tcl_assert(r >= 0);\n+\t\tif (r > 0)\n+\t\t\tbreak;\n+\t\tcl_assert_equal_s(names[j], ref.refname);\n+\t\tcl_assert_equal_i(update_index, ref.update_index);\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tcl_assert_equal_i(j, N);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&buf);\n+\tfree_names(names);\n+}\n+\n+void test_reftable_readwrite__table_write_small_table(void)\n+{\n+\tchar **names;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tint N = 1;\n+\twrite_table(&names, &buf, N, 4096, REFTABLE_HASH_SHA1);\n+\tcl_assert(buf.len < 200);\n+\treftable_buf_release(&buf);\n+\tfree_names(names);\n+}\n+\n+void test_reftable_readwrite__table_read_api(void)\n+{\n+\tchar **names;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_log_record log = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\n+\twrite_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"file.ref\") == 0);\n+\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_ref(&it, names[0]) == 0);\n+\n+\tcl_assert_equal_i(reftable_iterator_next_log(&it, &log), REFTABLE_API_ERROR);\n+\n+\treftable_buf_release(&buf);\n+\tfree_names(names);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&buf);\n+}\n+\n+static void t_table_read_write_seek(int index, enum reftable_hash hash_id)\n+{\n+\tchar **names;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tint N = 50;\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_block_source source = { 0 };\n+\tint err;\n+\tint i = 0;\n+\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_buf pastLast = REFTABLE_BUF_INIT;\n+\tstruct reftable_ref_record ref = { 0 };\n+\n+\twrite_table(&names, &buf, N, 256, hash_id);\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"file.ref\") == 0);\n+\tcl_assert_equal_i(hash_id, reftable_reader_hash_id(reader));\n+\n+\tif (!index) {\n+\t\treader->ref_offsets.index_offset = 0;\n+\t} else {\n+\t\tcl_assert(reader->ref_offsets.index_offset > 0);\n+\t}\n+\n+\tfor (i = 1; i < N; i++) {\n+\t\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\t\tcl_assert(reftable_iterator_seek_ref(&it, names[i]) == 0);\n+\t\tcl_assert(reftable_iterator_next_ref(&it, &ref) == 0);\n+\t\tcl_assert_equal_s(names[i], ref.refname);\n+\t\tcl_assert_equal_i(REFTABLE_REF_VAL1, ref.value_type);\n+\t\tcl_assert_equal_i(i, ref.value.val1[0]);\n+\n+\t\treftable_ref_record_release(&ref);\n+\t\treftable_iterator_destroy(&it);\n+\t}\n+\n+\tcl_assert(reftable_buf_addstr(&pastLast, names[N - 1]) == 0);\n+\tcl_assert(reftable_buf_addstr(&pastLast, \"/\") == 0);\n+\n+\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\terr = reftable_iterator_seek_ref(&it, pastLast.buf);\n+\tif (err == 0) {\n+\t\tstruct reftable_ref_record ref = { 0 };\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tcl_assert(err > 0);\n+\t} else {\n+\t\tcl_assert(err > 0);\n+\t}\n+\n+\treftable_buf_release(&pastLast);\n+\treftable_iterator_destroy(&it);\n+\n+\treftable_buf_release(&buf);\n+\tfree_names(names);\n+\treftable_reader_decref(reader);\n+}\n+\n+void test_reftable_readwrite__table_read_write_seek_linear(void)\n+{\n+\tt_table_read_write_seek(0, REFTABLE_HASH_SHA1);\n+}\n+\n+void test_reftable_readwrite__table_read_write_seek_linear_sha256(void)\n+{\n+\tt_table_read_write_seek(0, REFTABLE_HASH_SHA256);\n+}\n+\n+void test_reftable_readwrite__table_read_write_seek_index(void)\n+{\n+\tt_table_read_write_seek(1, REFTABLE_HASH_SHA1);\n+}\n+\n+static void t_table_refs_for(int indexed)\n+{\n+\tchar **want_names;\n+\tint want_names_len = 0;\n+\tuint8_t want_hash[REFTABLE_HASH_SIZE_SHA1];\n+\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 256,\n+\t};\n+\tstruct reftable_ref_record ref = { 0 };\n+\tstruct reftable_reader *reader;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_iterator it = { 0 };\n+\tint N = 50, j, i;\n+\n+\twant_names = reftable_calloc(N + 1, sizeof(*want_names));\n+\tcl_assert(want_names != NULL);\n+\n+\tcl_reftable_set_hash(want_hash, 4, REFTABLE_HASH_SHA1);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tuint8_t hash[REFTABLE_HASH_SIZE_SHA1];\n+\t\tchar fill[51] = { 0 };\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = { 0 };\n+\n+\t\tmemset(hash, i, sizeof(hash));\n+\t\tmemset(fill, 'x', 50);\n+\t\t/* Put the variable part in the start */\n+\t\tsnprintf(name, sizeof(name), \"br%02d%s\", i, fill);\n+\t\tname[40] = 0;\n+\t\tref.refname = name;\n+\n+\t\tref.value_type = REFTABLE_REF_VAL2;\n+\t\tcl_reftable_set_hash(ref.value.val2.value, i / 4,\n+\t\t\t\t    REFTABLE_HASH_SHA1);\n+\t\tcl_reftable_set_hash(ref.value.val2.target_value, 3 + i / 4,\n+\t\t\t\t    REFTABLE_HASH_SHA1);\n+\n+\t\t/* 80 bytes / entry, so 3 entries per block. Yields 17\n+\t\t */\n+\t\t/* blocks. */\n+\t\tcl_assert_equal_i(reftable_writer_add_ref(w, &ref), 0);\n+\n+\t\tif (!memcmp(ref.value.val2.value, want_hash, REFTABLE_HASH_SIZE_SHA1) ||\n+\t\t    !memcmp(ref.value.val2.target_value, want_hash, REFTABLE_HASH_SIZE_SHA1))\n+\t\t\twant_names[want_names_len++] = xstrdup(name);\n+\t}\n+\n+\tcl_assert_equal_i(reftable_writer_close(w), 0);\n+\n+\treftable_writer_free(w);\n+\tw = NULL;\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&reader, &source, \"file.ref\") == 0);\n+\tif (!indexed)\n+\t\treader->obj_offsets.is_present = 0;\n+\n+\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\treftable_iterator_destroy(&it);\n+\n+\tcl_assert(reftable_reader_refs_for(reader, &it, want_hash) == 0);\n+\n+\tfor (j = 0; ; j++) {\n+\t\tint err = reftable_iterator_next_ref(&it, &ref);\n+\t\tcl_assert(err >= 0);\n+\t\tif (err > 0)\n+\t\t\tbreak;\n+\t\tcl_assert(j < want_names_len);\n+\t\tcl_assert_equal_s(ref.refname, want_names[j]);\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tcl_assert_equal_i(j, want_names_len);\n+\n+\treftable_buf_release(&buf);\n+\tfree_names(want_names);\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_decref(reader);\n+}\n+\n+void test_reftable_readwrite__table_refs_for_no_index(void)\n+{\n+\tt_table_refs_for(0);\n+}\n+\n+void test_reftable_readwrite__table_refs_for_obj_index(void)\n+{\n+\tt_table_refs_for(1);\n+}\n+\n+void test_reftable_readwrite__write_empty_table(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_reader *rd = NULL;\n+\tstruct reftable_ref_record rec = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\tcl_assert_equal_i(reftable_writer_close(w), REFTABLE_EMPTY_TABLE_ERROR);\n+\treftable_writer_free(w);\n+\n+\tcl_assert_equal_i(buf.len, header_size(1) + footer_size(1));\n+\n+\tblock_source_from_buf(&source, &buf);\n+\n+\tcl_assert(reftable_reader_new(&rd, &source, \"filename\") == 0);\n+\tcl_assert(reftable_reader_init_ref_iterator(rd, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) > 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_reader_decref(rd);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__write_object_id_min_length(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 75,\n+\t};\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_ref_record ref = {\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = {42},\n+\t};\n+\tint i;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\t/* Write the same hash in many refs. If there is only 1 hash, the\n+\t * disambiguating prefix is length 0 */\n+\tfor (i = 0; i < 256; i++) {\n+\t\tchar name[256];\n+\t\tsnprintf(name, sizeof(name), \"ref%05d\", i);\n+\t\tref.refname = name;\n+\t\tcl_assert(reftable_writer_add_ref(w, &ref) == 0);\n+\t}\n+\n+\tcl_assert(reftable_writer_close(w) == 0);\n+\tcl_assert_equal_i(reftable_writer_stats(w)->object_id_len, 2);\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__write_object_id_length(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 75,\n+\t};\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_ref_record ref = {\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = {42},\n+\t};\n+\tint i;\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\n+\t/* Write the same hash in many refs. If there is only 1 hash, the\n+\t * disambiguating prefix is length 0 */\n+\tfor (i = 0; i < 256; i++) {\n+\t\tchar name[256];\n+\t\tsnprintf(name, sizeof(name), \"ref%05d\", i);\n+\t\tref.refname = name;\n+\t\tref.value.val1[15] = i;\n+\t\tcl_assert(reftable_writer_add_ref(w, &ref) == 0);\n+\t}\n+\n+\tcl_assert(reftable_writer_close(w) == 0);\n+\tcl_assert_equal_i(reftable_writer_stats(w)->object_id_len, 16);\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__write_empty_key(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *) \"\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_DELETION,\n+\t};\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\tcl_assert_equal_i(reftable_writer_add_ref(w, &ref), REFTABLE_API_ERROR);\n+\tcl_assert_equal_i(reftable_writer_close(w), REFTABLE_EMPTY_TABLE_ERROR);\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__write_key_order(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);\n+\tstruct reftable_ref_record refs[2] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"b\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = (char *) \"target\",\n+\t\t\t},\n+\t\t}, {\n+\t\t\t.refname = (char *) \"a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value = {\n+\t\t\t\t.symref = (char *) \"target\",\n+\t\t\t},\n+\t\t}\n+\t};\n+\n+\treftable_writer_set_limits(w, 1, 1);\n+\tcl_assert(reftable_writer_add_ref(w, &refs[0]) == 0);\n+\tcl_assert_equal_i(reftable_writer_add_ref(w, &refs[1]), REFTABLE_API_ERROR);\n+\n+\trefs[0].update_index = 2;\n+\tcl_assert_equal_i(reftable_writer_add_ref(w, &refs[0]), REFTABLE_API_ERROR);\n+\n+\treftable_writer_close(w);\n+\treftable_writer_free(w);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__write_multiple_indices(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 100,\n+\t};\n+\tstruct reftable_buf writer_buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tconst struct reftable_stats *stats;\n+\tstruct reftable_writer *writer;\n+\tstruct reftable_reader *reader;\n+\tchar buf[128];\n+\tint i;\n+\n+\twriter = cl_reftable_strbuf_writer(&writer_buf, &opts);\n+\treftable_writer_set_limits(writer, 1, 1);\n+\tfor (i = 0; i < 100; i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = {i},\n+\t\t};\n+\n+\t\tsnprintf(buf, sizeof(buf), \"refs/heads/%04d\", i);\n+\t\tref.refname = buf;\n+\n+\t\tcl_assert(reftable_writer_add_ref(writer, &ref) == 0);\n+\t}\n+\n+\tfor (i = 0; i < 100; i++) {\n+\t\tstruct reftable_log_record log = {\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value.update = {\n+\t\t\t\t.old_hash = { i },\n+\t\t\t\t.new_hash = { i },\n+\t\t\t},\n+\t\t};\n+\n+\t\tsnprintf(buf, sizeof(buf), \"refs/heads/%04d\", i);\n+\t\tlog.refname = buf;\n+\n+\t\tcl_assert(reftable_writer_add_log(writer, &log) == 0);\n+\t}\n+\n+\treftable_writer_close(writer);\n+\n+\t/*\n+\t * The written data should be sufficiently large to result in indices\n+\t * for each of the block types.\n+\t */\n+\tstats = reftable_writer_stats(writer);\n+\tcl_assert(stats->ref_stats.index_offset > 0);\n+\tcl_assert(stats->obj_stats.index_offset > 0);\n+\tcl_assert(stats->log_stats.index_offset > 0);\n+\n+\tblock_source_from_buf(&source, &writer_buf);\n+\tcl_assert(reftable_reader_new(&reader, &source, \"filename\") == 0);\n+\n+\t/*\n+\t * Seeking the log uses the log index now. In case there is any\n+\t * confusion regarding indices we would notice here.\n+\t */\n+\tcl_assert(reftable_reader_init_log_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_log(&it, \"\") == 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_writer_free(writer);\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&writer_buf);\n+}\n+\n+void test_reftable_readwrite__write_multi_level_index(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.block_size = 100,\n+\t};\n+\tstruct reftable_buf writer_buf = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tconst struct reftable_stats *stats;\n+\tstruct reftable_writer *writer;\n+\tstruct reftable_reader *reader;\n+\n+\twriter = cl_reftable_strbuf_writer(&writer_buf, &opts);\n+\treftable_writer_set_limits(writer, 1, 1);\n+\tfor (size_t i = 0; i < 200; i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = {i},\n+\t\t};\n+\t\tchar buf[128];\n+\n+\t\tsnprintf(buf, sizeof(buf), \"refs/heads/%03\" PRIuMAX, (uintmax_t)i);\n+\t\tref.refname = buf;\n+\n+\t\tcl_assert(reftable_writer_add_ref(writer, &ref) == 0);\n+\t}\n+\treftable_writer_close(writer);\n+\n+\t/*\n+\t * The written refs should be sufficiently large to result in a\n+\t * multi-level index.\n+\t */\n+\tstats = reftable_writer_stats(writer);\n+\tcl_assert_equal_i(stats->ref_stats.max_index_level, 2);\n+\n+\tblock_source_from_buf(&source, &writer_buf);\n+\tcl_assert(reftable_reader_new(&reader, &source, \"filename\") == 0);\n+\n+\t/*\n+\t * Seeking the last ref should work as expected.\n+\t */\n+\tcl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"refs/heads/199\") == 0);\n+\n+\treftable_iterator_destroy(&it);\n+\treftable_writer_free(writer);\n+\treftable_reader_decref(reader);\n+\treftable_buf_release(&writer_buf);\n+\treftable_buf_release(&buf);\n+}\n+\n+void test_reftable_readwrite__corrupt_table_empty(void)\n+{\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_reader *reader;\n+\n+\tblock_source_from_buf(&source, &buf);\n+\tcl_assert_equal_i(reftable_reader_new(&reader, &source,\n+\t\t\t\t\t\t\t\t\t\t  \"file.log\"),REFTABLE_FORMAT_ERROR);\n+}\n+\n+void test_reftable_readwrite__corrupt_table(void)\n+{\n+\tuint8_t zeros[1024] = { 0 };\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tstruct reftable_block_source source = { 0 };\n+\tstruct reftable_reader *reader;\n+\tcl_assert(reftable_buf_add(&buf, zeros, sizeof(zeros)) == 0);\n+\n+\tblock_source_from_buf(&source, &buf);\n+\tcl_assert_equal_i(reftable_reader_new(&reader, &source,\n+\t\t\t\t\t\t\t\t\t\t  \"file.log\"), REFTABLE_FORMAT_ERROR);\n+\n+\treftable_buf_release(&buf);\n+}\n-- \n2.43.0\n\n"},{"id":"516863","messageId":"20250427185351.82520-9-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 8/9] t/unit-tests: convert reftable record test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:50Z","receivedAt":"2025-04-27T18:55:12Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable record test file to use clar by using clar assertions\nwhere necessary.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                         |   2 +-\n t/meson.build                    |   2 +-\n t/unit-tests/t-reftable-record.c | 585 -------------------------------\n t/unit-tests/u-reftable-record.c | 565 +++++++++++++++++++++++++++++\n 4 files changed, 567 insertions(+), 587 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-record.c\n create mode 100644 t/unit-tests/u-reftable-record.c\n\ndiff --git a/Makefile b/Makefile\nindex 7511959baf..1af0eb8aa2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1368,6 +1368,7 @@ CLAR_TEST_SUITES += u-reftable-merged\n CLAR_TEST_SUITES += u-reftable-pq\n CLAR_TEST_SUITES += u-reftable-reader\n CLAR_TEST_SUITES += u-reftable-readwrite\n+CLAR_TEST_SUITES += u-reftable-record\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1381,7 +1382,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-record\n UNIT_TEST_PROGRAMS += t-reftable-stack\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\ndiff --git a/t/meson.build b/t/meson.build\nindex b9e9648f7c..bd0292f0c7 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -14,6 +14,7 @@ clar_test_suites = [\n   'unit-tests/u-reftable-pq.c',\n   'unit-tests/u-reftable-reader.c',\n   'unit-tests/u-reftable-readwrite.c',\n+  'unit-tests/u-reftable-record.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -61,7 +62,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-record.c',\n   'unit-tests/t-reftable-stack.c',\n ]\n \ndiff --git a/t/unit-tests/t-reftable-record.c b/t/unit-tests/t-reftable-record.c\ndeleted file mode 100644\nindex 5954966373..0000000000\n--- a/t/unit-tests/t-reftable-record.c\n+++ /dev/null\n@@ -1,585 +0,0 @@\n-/*\n-  Copyright 2020 Google LLC\n-\n-  Use of this source code is governed by a BSD-style\n-  license that can be found in the LICENSE file or at\n-  https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#include \"test-lib.h\"\n-#include \"reftable/basics.h\"\n-#include \"reftable/constants.h\"\n-#include \"reftable/record.h\"\n-\n-static void t_copy(struct reftable_record *rec)\n-{\n-\tstruct reftable_record copy;\n-\tuint8_t typ;\n-\n-\ttyp = reftable_record_type(rec);\n-\tcheck(!reftable_record_init(&copy, typ));\n-\treftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);\n-\t/* do it twice to catch memory leaks */\n-\treftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);\n-\tcheck(reftable_record_equal(rec, &copy, REFTABLE_HASH_SIZE_SHA1));\n-\n-\treftable_record_release(&copy);\n-}\n-\n-static void t_varint_roundtrip(void)\n-{\n-\tuint64_t inputs[] = { 0,\n-\t\t\t      1,\n-\t\t\t      27,\n-\t\t\t      127,\n-\t\t\t      128,\n-\t\t\t      257,\n-\t\t\t      4096,\n-\t\t\t      ((uint64_t)1 << 63),\n-\t\t\t      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(inputs); i++) {\n-\t\tuint8_t dest[10];\n-\n-\t\tstruct string_view out = {\n-\t\t\t.buf = dest,\n-\t\t\t.len = sizeof(dest),\n-\t\t};\n-\t\tuint64_t in = inputs[i];\n-\t\tint n = put_var_int(&out, in);\n-\t\tuint64_t got = 0;\n-\n-\t\tcheck_int(n, >, 0);\n-\t\tout.len = n;\n-\t\tn = get_var_int(&got, &out);\n-\t\tcheck_int(n, >, 0);\n-\n-\t\tcheck_int(got, ==, in);\n-\t}\n-}\n-\n-static void t_varint_overflow(void)\n-{\n-\tunsigned char buf[] = {\n-\t\t0xFF, 0xFF, 0xFF, 0xFF,\n-\t\t0xFF, 0xFF, 0xFF, 0xFF,\n-\t\t0xFF, 0x00,\n-\t};\n-\tstruct string_view view = {\n-\t\t.buf = buf,\n-\t\t.len = sizeof(buf),\n-\t};\n-\tuint64_t value;\n-\tint err = get_var_int(&value, &view);\n-\tcheck_int(err, ==, -1);\n-}\n-\n-static void set_hash(uint8_t *h, int j)\n-{\n-\tfor (size_t i = 0; i < hash_size(REFTABLE_HASH_SHA1); i++)\n-\t\th[i] = (j >> i) & 0xff;\n-}\n-\n-static void t_reftable_ref_record_comparison(void)\n-{\n-\tstruct reftable_record in[3] = {\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_REF,\n-\t\t\t.u.ref.refname = (char *) \"refs/heads/master\",\n-\t\t\t.u.ref.value_type = REFTABLE_REF_VAL1,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_REF,\n-\t\t\t.u.ref.refname = (char *) \"refs/heads/master\",\n-\t\t\t.u.ref.value_type = REFTABLE_REF_DELETION,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_REF,\n-\t\t\t.u.ref.refname = (char *) \"HEAD\",\n-\t\t\t.u.ref.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.u.ref.value.symref = (char *) \"refs/heads/master\",\n-\t\t},\n-\t};\n-\tint cmp;\n-\n-\tcheck(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck(!cmp);\n-\n-\tcheck(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[1], &in[2], &cmp));\n-\tcheck_int(cmp, >, 0);\n-\n-\tin[1].u.ref.value_type = in[0].u.ref.value_type;\n-\tcheck(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck(!cmp);\n-}\n-\n-static void t_reftable_ref_record_compare_name(void)\n-{\n-\tstruct reftable_ref_record recs[3] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/a\"\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/b\"\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/a\"\n-\t\t},\n-\t};\n-\n-\tcheck_int(reftable_ref_record_compare_name(&recs[0], &recs[1]), <, 0);\n-\tcheck_int(reftable_ref_record_compare_name(&recs[1], &recs[0]), >, 0);\n-\tcheck_int(reftable_ref_record_compare_name(&recs[0], &recs[2]), ==, 0);\n-}\n-\n-static void t_reftable_ref_record_roundtrip(void)\n-{\n-\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n-\n-\tfor (int i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {\n-\t\tstruct reftable_record in = {\n-\t\t\t.type = BLOCK_TYPE_REF,\n-\t\t\t.u.ref.value_type = i,\n-\t\t};\n-\t\tstruct reftable_record out = { .type = BLOCK_TYPE_REF };\n-\t\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n-\t\tuint8_t buffer[1024] = { 0 };\n-\t\tstruct string_view dest = {\n-\t\t\t.buf = buffer,\n-\t\t\t.len = sizeof(buffer),\n-\t\t};\n-\t\tint n, m;\n-\n-\t\tin.u.ref.value_type = i;\n-\t\tswitch (i) {\n-\t\tcase REFTABLE_REF_DELETION:\n-\t\t\tbreak;\n-\t\tcase REFTABLE_REF_VAL1:\n-\t\t\tset_hash(in.u.ref.value.val1, 1);\n-\t\t\tbreak;\n-\t\tcase REFTABLE_REF_VAL2:\n-\t\t\tset_hash(in.u.ref.value.val2.value, 1);\n-\t\t\tset_hash(in.u.ref.value.val2.target_value, 2);\n-\t\t\tbreak;\n-\t\tcase REFTABLE_REF_SYMREF:\n-\t\t\tin.u.ref.value.symref = xstrdup(\"target\");\n-\t\t\tbreak;\n-\t\t}\n-\t\tin.u.ref.refname = xstrdup(\"refs/heads/master\");\n-\n-\t\tt_copy(&in);\n-\n-\t\tcheck_int(reftable_record_val_type(&in), ==, i);\n-\t\tcheck_int(reftable_record_is_deletion(&in), ==, i == REFTABLE_REF_DELETION);\n-\n-\t\treftable_record_key(&in, &key);\n-\t\tn = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);\n-\t\tcheck_int(n, >, 0);\n-\n-\t\t/* decode into a non-zero reftable_record to test for leaks. */\n-\t\tm = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch);\n-\t\tcheck_int(n, ==, m);\n-\n-\t\tcheck(reftable_ref_record_equal(&in.u.ref, &out.u.ref,\n-\t\t\t\t\t\t REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_record_release(&in);\n-\n-\t\treftable_buf_release(&key);\n-\t\treftable_record_release(&out);\n-\t}\n-\n-\treftable_buf_release(&scratch);\n-}\n-\n-static void t_reftable_log_record_comparison(void)\n-{\n-\tstruct reftable_record in[3] = {\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_LOG,\n-\t\t\t.u.log.refname = (char *) \"refs/heads/master\",\n-\t\t\t.u.log.update_index = 42,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_LOG,\n-\t\t\t.u.log.refname = (char *) \"refs/heads/master\",\n-\t\t\t.u.log.update_index = 22,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_LOG,\n-\t\t\t.u.log.refname = (char *) \"refs/heads/main\",\n-\t\t\t.u.log.update_index = 22,\n-\t\t},\n-\t};\n-\tint cmp;\n-\n-\tcheck(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[1], &in[2], &cmp));\n-\tcheck_int(cmp, >, 0);\n-\t/* comparison should be reversed for equal keys, because\n-\t * comparison is now performed on the basis of update indices */\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck_int(cmp, <, 0);\n-\n-\tin[1].u.log.update_index = in[0].u.log.update_index;\n-\tcheck(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-}\n-\n-static void t_reftable_log_record_compare_key(void)\n-{\n-\tstruct reftable_log_record logs[3] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/a\",\n-\t\t\t.update_index = 1,\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/b\",\n-\t\t\t.update_index = 2,\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/a\",\n-\t\t\t.update_index = 3,\n-\t\t},\n-\t};\n-\n-\tcheck_int(reftable_log_record_compare_key(&logs[0], &logs[1]), <, 0);\n-\tcheck_int(reftable_log_record_compare_key(&logs[1], &logs[0]), >, 0);\n-\n-\tlogs[1].update_index = logs[0].update_index;\n-\tcheck_int(reftable_log_record_compare_key(&logs[0], &logs[1]), <, 0);\n-\n-\tcheck_int(reftable_log_record_compare_key(&logs[0], &logs[2]), >, 0);\n-\tcheck_int(reftable_log_record_compare_key(&logs[2], &logs[0]), <, 0);\n-\tlogs[2].update_index = logs[0].update_index;\n-\tcheck_int(reftable_log_record_compare_key(&logs[0], &logs[2]), ==, 0);\n-}\n-\n-static void t_reftable_log_record_roundtrip(void)\n-{\n-\tstruct reftable_log_record in[] = {\n-\t\t{\n-\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n-\t\t\t.update_index = 42,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t.value = {\n-\t\t\t\t.update = {\n-\t\t\t\t\t.name = xstrdup(\"han-wen\"),\n-\t\t\t\t\t.email = xstrdup(\"hanwen@google.com\"),\n-\t\t\t\t\t.message = xstrdup(\"test\"),\n-\t\t\t\t\t.time = 1577123507,\n-\t\t\t\t\t.tz_offset = 100,\n-\t\t\t\t},\n-\t\t\t}\n-\t\t},\n-\t\t{\n-\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n-\t\t\t.update_index = 22,\n-\t\t\t.value_type = REFTABLE_LOG_DELETION,\n-\t\t},\n-\t\t{\n-\t\t\t.refname = xstrdup(\"branch\"),\n-\t\t\t.update_index = 33,\n-\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t}\n-\t};\n-\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n-\tset_hash(in[0].value.update.new_hash, 1);\n-\tset_hash(in[0].value.update.old_hash, 2);\n-\tset_hash(in[2].value.update.new_hash, 3);\n-\tset_hash(in[2].value.update.old_hash, 4);\n-\n-\tcheck(!reftable_log_record_is_deletion(&in[0]));\n-\tcheck(reftable_log_record_is_deletion(&in[1]));\n-\tcheck(!reftable_log_record_is_deletion(&in[2]));\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(in); i++) {\n-\t\tstruct reftable_record rec = { .type = BLOCK_TYPE_LOG };\n-\t\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n-\t\tuint8_t buffer[1024] = { 0 };\n-\t\tstruct string_view dest = {\n-\t\t\t.buf = buffer,\n-\t\t\t.len = sizeof(buffer),\n-\t\t};\n-\t\t/* populate out, to check for leaks. */\n-\t\tstruct reftable_record out = {\n-\t\t\t.type = BLOCK_TYPE_LOG,\n-\t\t\t.u.log = {\n-\t\t\t\t.refname = xstrdup(\"old name\"),\n-\t\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t\t\t.value = {\n-\t\t\t\t\t.update = {\n-\t\t\t\t\t\t.name = xstrdup(\"old name\"),\n-\t\t\t\t\t\t.email = xstrdup(\"old@email\"),\n-\t\t\t\t\t\t.message = xstrdup(\"old message\"),\n-\t\t\t\t\t},\n-\t\t\t\t},\n-\t\t\t},\n-\t\t};\n-\t\tint n, m, valtype;\n-\n-\t\trec.u.log = in[i];\n-\n-\t\tt_copy(&rec);\n-\n-\t\treftable_record_key(&rec, &key);\n-\n-\t\tn = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1);\n-\t\tcheck_int(n, >=, 0);\n-\t\tvaltype = reftable_record_val_type(&rec);\n-\t\tm = reftable_record_decode(&out, key, valtype, dest,\n-\t\t\t\t\t   REFTABLE_HASH_SIZE_SHA1, &scratch);\n-\t\tcheck_int(n, ==, m);\n-\n-\t\tcheck(reftable_log_record_equal(&in[i], &out.u.log,\n-\t\t\t\t\t\t REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_log_record_release(&in[i]);\n-\t\treftable_buf_release(&key);\n-\t\treftable_record_release(&out);\n-\t}\n-\n-\treftable_buf_release(&scratch);\n-}\n-\n-static void t_key_roundtrip(void)\n-{\n-\tuint8_t buffer[1024] = { 0 };\n-\tstruct string_view dest = {\n-\t\t.buf = buffer,\n-\t\t.len = sizeof(buffer),\n-\t};\n-\tstruct reftable_buf last_key = REFTABLE_BUF_INIT;\n-\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n-\tstruct reftable_buf roundtrip = REFTABLE_BUF_INIT;\n-\tint restart;\n-\tuint8_t extra;\n-\tint n, m;\n-\tuint8_t rt_extra;\n-\n-\tcheck(!reftable_buf_addstr(&last_key, \"refs/heads/master\"));\n-\tcheck(!reftable_buf_addstr(&key, \"refs/tags/bla\"));\n-\textra = 6;\n-\tn = reftable_encode_key(&restart, dest, last_key, key, extra);\n-\tcheck(!restart);\n-\tcheck_int(n, >, 0);\n-\n-\tcheck(!reftable_buf_addstr(&roundtrip, \"refs/heads/master\"));\n-\tm = reftable_decode_key(&roundtrip, &rt_extra, dest);\n-\tcheck_int(n, ==, m);\n-\tcheck(!reftable_buf_cmp(&key, &roundtrip));\n-\tcheck_int(rt_extra, ==, extra);\n-\n-\treftable_buf_release(&last_key);\n-\treftable_buf_release(&key);\n-\treftable_buf_release(&roundtrip);\n-}\n-\n-static void t_reftable_obj_record_comparison(void)\n-{\n-\n-\tuint8_t id_bytes[] = { 0, 1, 2, 3, 4, 5, 6 };\n-\tuint64_t offsets[] = { 0, 16, 32, 48, 64, 80, 96, 112};\n-\tstruct reftable_record in[3] = {\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_OBJ,\n-\t\t\t.u.obj.hash_prefix = id_bytes,\n-\t\t\t.u.obj.hash_prefix_len = 7,\n-\t\t\t.u.obj.offsets = offsets,\n-\t\t\t.u.obj.offset_len = 8,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_OBJ,\n-\t\t\t.u.obj.hash_prefix = id_bytes,\n-\t\t\t.u.obj.hash_prefix_len = 7,\n-\t\t\t.u.obj.offsets = offsets,\n-\t\t\t.u.obj.offset_len = 5,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_OBJ,\n-\t\t\t.u.obj.hash_prefix = id_bytes,\n-\t\t\t.u.obj.hash_prefix_len = 5,\n-\t\t},\n-\t};\n-\tint cmp;\n-\n-\tcheck(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck(!cmp);\n-\n-\tcheck(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[1], &in[2], &cmp));\n-\tcheck_int(cmp, >, 0);\n-\n-\tin[1].u.obj.offset_len = in[0].u.obj.offset_len;\n-\tcheck(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck(!cmp);\n-}\n-\n-static void t_reftable_obj_record_roundtrip(void)\n-{\n-\tuint8_t testHash1[REFTABLE_HASH_SIZE_SHA1] = { 1, 2, 3, 4, 0 };\n-\tuint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };\n-\tstruct reftable_obj_record recs[3] = {\n-\t\t{\n-\t\t\t.hash_prefix = testHash1,\n-\t\t\t.hash_prefix_len = 5,\n-\t\t\t.offsets = till9,\n-\t\t\t.offset_len = 3,\n-\t\t},\n-\t\t{\n-\t\t\t.hash_prefix = testHash1,\n-\t\t\t.hash_prefix_len = 5,\n-\t\t\t.offsets = till9,\n-\t\t\t.offset_len = 9,\n-\t\t},\n-\t\t{\n-\t\t\t.hash_prefix = testHash1,\n-\t\t\t.hash_prefix_len = 5,\n-\t\t},\n-\t};\n-\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(recs); i++) {\n-\t\tuint8_t buffer[1024] = { 0 };\n-\t\tstruct string_view dest = {\n-\t\t\t.buf = buffer,\n-\t\t\t.len = sizeof(buffer),\n-\t\t};\n-\t\tstruct reftable_record in = {\n-\t\t\t.type = BLOCK_TYPE_OBJ,\n-\t\t\t.u = {\n-\t\t\t\t.obj = recs[i],\n-\t\t\t},\n-\t\t};\n-\t\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n-\t\tstruct reftable_record out = { .type = BLOCK_TYPE_OBJ };\n-\t\tint n, m;\n-\t\tuint8_t extra;\n-\n-\t\tcheck(!reftable_record_is_deletion(&in));\n-\t\tt_copy(&in);\n-\t\treftable_record_key(&in, &key);\n-\t\tn = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);\n-\t\tcheck_int(n, >, 0);\n-\t\textra = reftable_record_val_type(&in);\n-\t\tm = reftable_record_decode(&out, key, extra, dest,\n-\t\t\t\t\t   REFTABLE_HASH_SIZE_SHA1, &scratch);\n-\t\tcheck_int(n, ==, m);\n-\n-\t\tcheck(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_buf_release(&key);\n-\t\treftable_record_release(&out);\n-\t}\n-\n-\treftable_buf_release(&scratch);\n-}\n-\n-static void t_reftable_index_record_comparison(void)\n-{\n-\tstruct reftable_record in[3] = {\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_INDEX,\n-\t\t\t.u.idx.offset = 22,\n-\t\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_INDEX,\n-\t\t\t.u.idx.offset = 32,\n-\t\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n-\t\t},\n-\t\t{\n-\t\t\t.type = BLOCK_TYPE_INDEX,\n-\t\t\t.u.idx.offset = 32,\n-\t\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n-\t\t},\n-\t};\n-\tint cmp;\n-\n-\tcheck(!reftable_buf_addstr(&in[0].u.idx.last_key, \"refs/heads/master\"));\n-\tcheck(!reftable_buf_addstr(&in[1].u.idx.last_key, \"refs/heads/master\"));\n-\tcheck(!reftable_buf_addstr(&in[2].u.idx.last_key, \"refs/heads/branch\"));\n-\n-\tcheck(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck(!cmp);\n-\n-\tcheck(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[1], &in[2], &cmp));\n-\tcheck_int(cmp, >, 0);\n-\n-\tin[1].u.idx.offset = in[0].u.idx.offset;\n-\tcheck(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));\n-\tcheck(!reftable_record_cmp(&in[0], &in[1], &cmp));\n-\tcheck(!cmp);\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(in); i++)\n-\t\treftable_record_release(&in[i]);\n-}\n-\n-static void t_reftable_index_record_roundtrip(void)\n-{\n-\tstruct reftable_record in = {\n-\t\t.type = BLOCK_TYPE_INDEX,\n-\t\t.u.idx = {\n-\t\t\t.offset = 42,\n-\t\t\t.last_key = REFTABLE_BUF_INIT,\n-\t\t},\n-\t};\n-\tuint8_t buffer[1024] = { 0 };\n-\tstruct string_view dest = {\n-\t\t.buf = buffer,\n-\t\t.len = sizeof(buffer),\n-\t};\n-\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n-\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n-\tstruct reftable_record out = {\n-\t\t.type = BLOCK_TYPE_INDEX,\n-\t\t.u.idx = { .last_key = REFTABLE_BUF_INIT },\n-\t};\n-\tint n, m;\n-\tuint8_t extra;\n-\n-\tcheck(!reftable_buf_addstr(&in.u.idx.last_key, \"refs/heads/master\"));\n-\treftable_record_key(&in, &key);\n-\tt_copy(&in);\n-\n-\tcheck(!reftable_record_is_deletion(&in));\n-\tcheck(!reftable_buf_cmp(&key, &in.u.idx.last_key));\n-\tn = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);\n-\tcheck_int(n, >, 0);\n-\n-\textra = reftable_record_val_type(&in);\n-\tm = reftable_record_decode(&out, key, extra, dest, REFTABLE_HASH_SIZE_SHA1,\n-\t\t\t\t   &scratch);\n-\tcheck_int(m, ==, n);\n-\n-\tcheck(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1));\n-\n-\treftable_record_release(&out);\n-\treftable_buf_release(&key);\n-\treftable_buf_release(&scratch);\n-\treftable_buf_release(&in.u.idx.last_key);\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_reftable_ref_record_comparison(), \"comparison operations work on ref record\");\n-\tTEST(t_reftable_log_record_comparison(), \"comparison operations work on log record\");\n-\tTEST(t_reftable_index_record_comparison(), \"comparison operations work on index record\");\n-\tTEST(t_reftable_obj_record_comparison(), \"comparison operations work on obj record\");\n-\tTEST(t_reftable_ref_record_compare_name(), \"reftable_ref_record_compare_name works\");\n-\tTEST(t_reftable_log_record_compare_key(), \"reftable_log_record_compare_key works\");\n-\tTEST(t_reftable_log_record_roundtrip(), \"record operations work on log record\");\n-\tTEST(t_reftable_ref_record_roundtrip(), \"record operations work on ref record\");\n-\tTEST(t_varint_roundtrip(), \"put_var_int and get_var_int work\");\n-\tTEST(t_varint_overflow(), \"get_var_int notices an integer overflow\");\n-\tTEST(t_key_roundtrip(), \"reftable_encode_key and reftable_decode_key work\");\n-\tTEST(t_reftable_obj_record_roundtrip(), \"record operations work on obj record\");\n-\tTEST(t_reftable_index_record_roundtrip(), \"record operations work on index record\");\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-record.c b/t/unit-tests/u-reftable-record.c\nnew file mode 100644\nindex 0000000000..ac2e33584c\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-record.c\n@@ -0,0 +1,565 @@\n+/*\n+  Copyright 2020 Google LLC\n+\n+  Use of this source code is governed by a BSD-style\n+  license that can be found in the LICENSE file or at\n+  https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#include \"unit-test.h\"\n+#include \"reftable/basics.h\"\n+#include \"reftable/constants.h\"\n+#include \"reftable/record.h\"\n+\n+static void t_copy(struct reftable_record *rec)\n+{\n+\tstruct reftable_record copy;\n+\tuint8_t typ;\n+\n+\ttyp = reftable_record_type(rec);\n+\tcl_assert(reftable_record_init(&copy, typ) == 0);\n+\treftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);\n+\t/* do it twice to catch memory leaks */\n+\treftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);\n+\tcl_assert(reftable_record_equal(rec, &copy, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\n+\treftable_record_release(&copy);\n+}\n+\n+void test_reftable_record__varint_roundtrip(void)\n+{\n+\tuint64_t inputs[] = { 0,\n+\t\t\t      1,\n+\t\t\t      27,\n+\t\t\t      127,\n+\t\t\t      128,\n+\t\t\t      257,\n+\t\t\t      4096,\n+\t\t\t      ((uint64_t)1 << 63),\n+\t\t\t      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(inputs); i++) {\n+\t\tuint8_t dest[10];\n+\n+\t\tstruct string_view out = {\n+\t\t\t.buf = dest,\n+\t\t\t.len = sizeof(dest),\n+\t\t};\n+\t\tuint64_t in = inputs[i];\n+\t\tint n = put_var_int(&out, in);\n+\t\tuint64_t got = 0;\n+\n+\t\tcl_assert(n > 0);\n+\t\tout.len = n;\n+\t\tn = get_var_int(&got, &out);\n+\t\tcl_assert(n > 0);\n+\n+\t\tcl_assert_equal_i(got, in);\n+\t}\n+}\n+\n+void test_reftable_record__varint_overflow(void)\n+{\n+\tunsigned char buf[] = {\n+\t\t0xFF, 0xFF, 0xFF, 0xFF,\n+\t\t0xFF, 0xFF, 0xFF, 0xFF,\n+\t\t0xFF, 0x00,\n+\t};\n+\tstruct string_view view = {\n+\t\t.buf = buf,\n+\t\t.len = sizeof(buf),\n+\t};\n+\tuint64_t value;\n+\tcl_assert_equal_i(get_var_int(&value, &view), -1);\n+}\n+\n+static void set_hash(uint8_t *h, int j)\n+{\n+\tfor (size_t i = 0; i < hash_size(REFTABLE_HASH_SHA1); i++)\n+\t\th[i] = (j >> i) & 0xff;\n+}\n+\n+void test_reftable_record__ref_record_comparison(void)\n+{\n+\tstruct reftable_record in[3] = {\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_REF,\n+\t\t\t.u.ref.refname = (char *) \"refs/heads/master\",\n+\t\t\t.u.ref.value_type = REFTABLE_REF_VAL1,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_REF,\n+\t\t\t.u.ref.refname = (char *) \"refs/heads/master\",\n+\t\t\t.u.ref.value_type = REFTABLE_REF_DELETION,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_REF,\n+\t\t\t.u.ref.refname = (char *) \"HEAD\",\n+\t\t\t.u.ref.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.u.ref.value.symref = (char *) \"refs/heads/master\",\n+\t\t},\n+\t};\n+\tint cmp;\n+\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp == 0);\n+\n+\tcl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);\n+\tcl_assert(cmp > 0);\n+\n+\tin[1].u.ref.value_type = in[0].u.ref.value_type;\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp == 0);\n+}\n+\n+void test_reftable_record__ref_record_compare_name(void)\n+{\n+\tstruct reftable_ref_record recs[3] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/a\"\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/b\"\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/a\"\n+\t\t},\n+\t};\n+\n+\tcl_assert(reftable_ref_record_compare_name(&recs[0], &recs[1]) < 0);\n+\tcl_assert(reftable_ref_record_compare_name(&recs[1], &recs[0]) > 0);\n+\tcl_assert_equal_i(reftable_ref_record_compare_name(&recs[0], &recs[2]), 0);\n+}\n+\n+void test_reftable_record__ref_record_roundtrip(void)\n+{\n+\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n+\n+\tfor (int i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {\n+\t\tstruct reftable_record in = {\n+\t\t\t.type = BLOCK_TYPE_REF,\n+\t\t\t.u.ref.value_type = i,\n+\t\t};\n+\t\tstruct reftable_record out = { .type = BLOCK_TYPE_REF };\n+\t\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\tint n, m;\n+\n+\t\tin.u.ref.value_type = i;\n+\t\tswitch (i) {\n+\t\tcase REFTABLE_REF_DELETION:\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL1:\n+\t\t\tset_hash(in.u.ref.value.val1, 1);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_VAL2:\n+\t\t\tset_hash(in.u.ref.value.val2.value, 1);\n+\t\t\tset_hash(in.u.ref.value.val2.target_value, 2);\n+\t\t\tbreak;\n+\t\tcase REFTABLE_REF_SYMREF:\n+\t\t\tin.u.ref.value.symref = xstrdup(\"target\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tin.u.ref.refname = xstrdup(\"refs/heads/master\");\n+\n+\t\tt_copy(&in);\n+\n+\t\tcl_assert_equal_i(reftable_record_val_type(&in), i);\n+\t\tcl_assert_equal_i(reftable_record_is_deletion(&in),  i == REFTABLE_REF_DELETION);\n+\n+\t\treftable_record_key(&in, &key);\n+\t\tn = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);\n+\t\tcl_assert(n > 0);\n+\n+\t\t/* decode into a non-zero reftable_record to test for leaks. */\n+\t\tm = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch);\n+\t\tcl_assert_equal_i(n, m);\n+\n+\t\tcl_assert(reftable_ref_record_equal(&in.u.ref, &out.u.ref,\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_record_release(&in);\n+\n+\t\treftable_buf_release(&key);\n+\t\treftable_record_release(&out);\n+\t}\n+\n+\treftable_buf_release(&scratch);\n+}\n+\n+void test_reftable_record__log_record_comparison(void)\n+{\n+\tstruct reftable_record in[3] = {\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_LOG,\n+\t\t\t.u.log.refname = (char *) \"refs/heads/master\",\n+\t\t\t.u.log.update_index = 42,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_LOG,\n+\t\t\t.u.log.refname = (char *) \"refs/heads/master\",\n+\t\t\t.u.log.update_index = 22,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_LOG,\n+\t\t\t.u.log.refname = (char *) \"refs/heads/main\",\n+\t\t\t.u.log.update_index = 22,\n+\t\t},\n+\t};\n+\tint cmp;\n+\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);\n+\tcl_assert(cmp > 0);\n+\t/* comparison should be reversed for equal keys, because\n+\t * comparison is now performed on the basis of update indices */\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp < 0);\n+\n+\tin[1].u.log.update_index = in[0].u.log.update_index;\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+}\n+\n+void test_reftable_record__log_record_compare_key(void)\n+{\n+\tstruct reftable_log_record logs[3] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/a\",\n+\t\t\t.update_index = 1,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/b\",\n+\t\t\t.update_index = 2,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/a\",\n+\t\t\t.update_index = 3,\n+\t\t},\n+\t};\n+\n+\tcl_assert(reftable_log_record_compare_key(&logs[0], &logs[1]) < 0);\n+\tcl_assert(reftable_log_record_compare_key(&logs[1], &logs[0]) > 0);\n+\n+\tlogs[1].update_index = logs[0].update_index;\n+\tcl_assert(reftable_log_record_compare_key(&logs[0], &logs[1]) < 0);\n+\n+\tcl_assert(reftable_log_record_compare_key(&logs[0], &logs[2]) > 0);\n+\tcl_assert(reftable_log_record_compare_key(&logs[2], &logs[0]) < 0);\n+\tlogs[2].update_index = logs[0].update_index;\n+\tcl_assert_equal_i(reftable_log_record_compare_key(&logs[0], &logs[2]), 0);\n+}\n+\n+void test_reftable_record__log_record_roundtrip(void)\n+{\n+\tstruct reftable_log_record in[] = {\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 42,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t.value = {\n+\t\t\t\t.update = {\n+\t\t\t\t\t.name = xstrdup(\"han-wen\"),\n+\t\t\t\t\t.email = xstrdup(\"hanwen@google.com\"),\n+\t\t\t\t\t.message = xstrdup(\"test\"),\n+\t\t\t\t\t.time = 1577123507,\n+\t\t\t\t\t.tz_offset = 100,\n+\t\t\t\t},\n+\t\t\t}\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"refs/heads/master\"),\n+\t\t\t.update_index = 22,\n+\t\t\t.value_type = REFTABLE_LOG_DELETION,\n+\t\t},\n+\t\t{\n+\t\t\t.refname = xstrdup(\"branch\"),\n+\t\t\t.update_index = 33,\n+\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t}\n+\t};\n+\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n+\tset_hash(in[0].value.update.new_hash, 1);\n+\tset_hash(in[0].value.update.old_hash, 2);\n+\tset_hash(in[2].value.update.new_hash, 3);\n+\tset_hash(in[2].value.update.old_hash, 4);\n+\n+\tcl_assert(reftable_log_record_is_deletion(&in[0]) == 0);\n+\tcl_assert(reftable_log_record_is_deletion(&in[1]) != 0);\n+\tcl_assert(reftable_log_record_is_deletion(&in[2]) == 0);\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(in); i++) {\n+\t\tstruct reftable_record rec = { .type = BLOCK_TYPE_LOG };\n+\t\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\t/* populate out, to check for leaks. */\n+\t\tstruct reftable_record out = {\n+\t\t\t.type = BLOCK_TYPE_LOG,\n+\t\t\t.u.log = {\n+\t\t\t\t.refname = xstrdup(\"old name\"),\n+\t\t\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t\t\t.value = {\n+\t\t\t\t\t.update = {\n+\t\t\t\t\t\t.name = xstrdup(\"old name\"),\n+\t\t\t\t\t\t.email = xstrdup(\"old@email\"),\n+\t\t\t\t\t\t.message = xstrdup(\"old message\"),\n+\t\t\t\t\t},\n+\t\t\t\t},\n+\t\t\t},\n+\t\t};\n+\t\tint n, m, valtype;\n+\n+\t\trec.u.log = in[i];\n+\n+\t\tt_copy(&rec);\n+\n+\t\treftable_record_key(&rec, &key);\n+\n+\t\tn = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1);\n+\t\tcl_assert(n >= 0);\n+\t\tvaltype = reftable_record_val_type(&rec);\n+\t\tm = reftable_record_decode(&out, key, valtype, dest,\n+\t\t\t\t\t   REFTABLE_HASH_SIZE_SHA1, &scratch);\n+\t\tcl_assert_equal_i(n, m);\n+\n+\t\tcl_assert(reftable_log_record_equal(&in[i], &out.u.log,\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_log_record_release(&in[i]);\n+\t\treftable_buf_release(&key);\n+\t\treftable_record_release(&out);\n+\t}\n+\n+\treftable_buf_release(&scratch);\n+}\n+\n+void test_reftable_record__key_roundtrip(void)\n+{\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct reftable_buf last_key = REFTABLE_BUF_INIT;\n+\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n+\tstruct reftable_buf roundtrip = REFTABLE_BUF_INIT;\n+\tint restart;\n+\tuint8_t extra;\n+\tint n, m;\n+\tuint8_t rt_extra;\n+\n+\tcl_assert(reftable_buf_addstr(&last_key, \"refs/heads/master\") == 0);\n+\tcl_assert(reftable_buf_addstr(&key, \"refs/tags/bla\") == 0);\n+\textra = 6;\n+\tn = reftable_encode_key(&restart, dest, last_key, key, extra);\n+\tcl_assert(restart == 0);\n+\tcl_assert(n > 0);\n+\n+\tcl_assert(reftable_buf_addstr(&roundtrip, \"refs/heads/master\") == 0);\n+\tm = reftable_decode_key(&roundtrip, &rt_extra, dest);\n+\tcl_assert_equal_i(n, m);\n+\tcl_assert(reftable_buf_cmp(&key, &roundtrip) == 0);\n+\tcl_assert_equal_i(rt_extra, extra);\n+\n+\treftable_buf_release(&last_key);\n+\treftable_buf_release(&key);\n+\treftable_buf_release(&roundtrip);\n+}\n+\n+void test_reftable_record__obj_record_comparison(void)\n+{\n+\n+\tuint8_t id_bytes[] = { 0, 1, 2, 3, 4, 5, 6 };\n+\tuint64_t offsets[] = { 0, 16, 32, 48, 64, 80, 96, 112};\n+\tstruct reftable_record in[3] = {\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_OBJ,\n+\t\t\t.u.obj.hash_prefix = id_bytes,\n+\t\t\t.u.obj.hash_prefix_len = 7,\n+\t\t\t.u.obj.offsets = offsets,\n+\t\t\t.u.obj.offset_len = 8,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_OBJ,\n+\t\t\t.u.obj.hash_prefix = id_bytes,\n+\t\t\t.u.obj.hash_prefix_len = 7,\n+\t\t\t.u.obj.offsets = offsets,\n+\t\t\t.u.obj.offset_len = 5,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_OBJ,\n+\t\t\t.u.obj.hash_prefix = id_bytes,\n+\t\t\t.u.obj.hash_prefix_len = 5,\n+\t\t},\n+\t};\n+\tint cmp;\n+\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp == 0);\n+\n+\tcl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);\n+\tcl_assert(cmp > 0);\n+\n+\tin[1].u.obj.offset_len = in[0].u.obj.offset_len;\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp == 0);\n+}\n+\n+void test_reftable_record__obj_record_roundtrip(void)\n+{\n+\tuint8_t testHash1[REFTABLE_HASH_SIZE_SHA1] = { 1, 2, 3, 4, 0 };\n+\tuint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };\n+\tstruct reftable_obj_record recs[3] = {\n+\t\t{\n+\t\t\t.hash_prefix = testHash1,\n+\t\t\t.hash_prefix_len = 5,\n+\t\t\t.offsets = till9,\n+\t\t\t.offset_len = 3,\n+\t\t},\n+\t\t{\n+\t\t\t.hash_prefix = testHash1,\n+\t\t\t.hash_prefix_len = 5,\n+\t\t\t.offsets = till9,\n+\t\t\t.offset_len = 9,\n+\t\t},\n+\t\t{\n+\t\t\t.hash_prefix = testHash1,\n+\t\t\t.hash_prefix_len = 5,\n+\t\t},\n+\t};\n+\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(recs); i++) {\n+\t\tuint8_t buffer[1024] = { 0 };\n+\t\tstruct string_view dest = {\n+\t\t\t.buf = buffer,\n+\t\t\t.len = sizeof(buffer),\n+\t\t};\n+\t\tstruct reftable_record in = {\n+\t\t\t.type = BLOCK_TYPE_OBJ,\n+\t\t\t.u = {\n+\t\t\t\t.obj = recs[i],\n+\t\t\t},\n+\t\t};\n+\t\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n+\t\tstruct reftable_record out = { .type = BLOCK_TYPE_OBJ };\n+\t\tint n, m;\n+\t\tuint8_t extra;\n+\n+\t\tcl_assert(reftable_record_is_deletion(&in) == 0);\n+\t\tt_copy(&in);\n+\t\treftable_record_key(&in, &key);\n+\t\tn = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);\n+\t\tcl_assert(n > 0);\n+\t\textra = reftable_record_val_type(&in);\n+\t\tm = reftable_record_decode(&out, key, extra, dest,\n+\t\t\t\t\t   REFTABLE_HASH_SIZE_SHA1, &scratch);\n+\t\tcl_assert_equal_i(n, m);\n+\n+\t\tcl_assert(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_buf_release(&key);\n+\t\treftable_record_release(&out);\n+\t}\n+\n+\treftable_buf_release(&scratch);\n+}\n+\n+void test_reftable_record__index_record_comparison(void)\n+{\n+\tstruct reftable_record in[3] = {\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_INDEX,\n+\t\t\t.u.idx.offset = 22,\n+\t\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_INDEX,\n+\t\t\t.u.idx.offset = 32,\n+\t\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n+\t\t},\n+\t\t{\n+\t\t\t.type = BLOCK_TYPE_INDEX,\n+\t\t\t.u.idx.offset = 32,\n+\t\t\t.u.idx.last_key = REFTABLE_BUF_INIT,\n+\t\t},\n+\t};\n+\tint cmp;\n+\n+\tcl_assert(reftable_buf_addstr(&in[0].u.idx.last_key, \"refs/heads/master\") == 0);\n+\tcl_assert(reftable_buf_addstr(&in[1].u.idx.last_key, \"refs/heads/master\") == 0);\n+\tcl_assert(reftable_buf_addstr(&in[2].u.idx.last_key, \"refs/heads/branch\") == 0);\n+\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp == 0);\n+\n+\tcl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);\n+\tcl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);\n+\tcl_assert(cmp > 0);\n+\n+\tin[1].u.idx.offset = in[0].u.idx.offset;\n+\tcl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);\n+\tcl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);\n+\tcl_assert(cmp == 0);\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(in); i++)\n+\t\treftable_record_release(&in[i]);\n+}\n+\n+void test_reftable_record__index_record_roundtrip(void)\n+{\n+\tstruct reftable_record in = {\n+\t\t.type = BLOCK_TYPE_INDEX,\n+\t\t.u.idx = {\n+\t\t\t.offset = 42,\n+\t\t\t.last_key = REFTABLE_BUF_INIT,\n+\t\t},\n+\t};\n+\tuint8_t buffer[1024] = { 0 };\n+\tstruct string_view dest = {\n+\t\t.buf = buffer,\n+\t\t.len = sizeof(buffer),\n+\t};\n+\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n+\tstruct reftable_buf key = REFTABLE_BUF_INIT;\n+\tstruct reftable_record out = {\n+\t\t.type = BLOCK_TYPE_INDEX,\n+\t\t.u.idx = { .last_key = REFTABLE_BUF_INIT },\n+\t};\n+\tint n, m;\n+\tuint8_t extra;\n+\n+\tcl_assert(reftable_buf_addstr(&in.u.idx.last_key, \"refs/heads/master\") == 0);\n+\treftable_record_key(&in, &key);\n+\tt_copy(&in);\n+\n+\tcl_assert(reftable_record_is_deletion(&in) == 0);\n+\tcl_assert(reftable_buf_cmp(&key, &in.u.idx.last_key) == 0);\n+\tn = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);\n+\tcl_assert(n > 0);\n+\n+\textra = reftable_record_val_type(&in);\n+\tm = reftable_record_decode(&out, key, extra, dest, REFTABLE_HASH_SIZE_SHA1,\n+\t\t\t\t   &scratch);\n+\tcl_assert_equal_i(m, n);\n+\n+\tcl_assert(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\n+\treftable_record_release(&out);\n+\treftable_buf_release(&key);\n+\treftable_buf_release(&scratch);\n+\treftable_buf_release(&in.u.idx.last_key);\n+}\n-- \n2.43.0\n\n"},{"id":"516864","messageId":"20250427185351.82520-10-kuforiji98@gmail.com","threadId":"63357","inReplyTo":"20250427185351.82520-1-kuforiji98@gmail.com","subject":"[PATCH 9/9] t/unit-tests: convert reftable stack test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-27T18:53:51Z","receivedAt":"2025-04-27T18:55:18Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Adapt reftable stack test file to use clar by using clar assertions\nwhere necessary.\n\nThis marks the end of all unit tests migrated away from the\n`unit-tests/t-*.c` pattern, there are no longer any files matching that\nglob. Remove the sanity check for `t-*.c` files to prevent Meson\nconfiguration errors during CI and local builds.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n Makefile                        |    2 +-\n t/meson.build                   |    3 +-\n t/unit-tests/t-reftable-stack.c | 1451 -------------------------------\n t/unit-tests/u-reftable-stack.c | 1247 ++++++++++++++++++++++++++\n 4 files changed, 1249 insertions(+), 1454 deletions(-)\n delete mode 100644 t/unit-tests/t-reftable-stack.c\n create mode 100644 t/unit-tests/u-reftable-stack.c\n\ndiff --git a/Makefile b/Makefile\nindex 1af0eb8aa2..7e646e16ee 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1369,6 +1369,7 @@ CLAR_TEST_SUITES += u-reftable-pq\n CLAR_TEST_SUITES += u-reftable-reader\n CLAR_TEST_SUITES += u-reftable-readwrite\n CLAR_TEST_SUITES += u-reftable-record\n+CLAR_TEST_SUITES += u-reftable-stack\n CLAR_TEST_SUITES += u-reftable-tree\n CLAR_TEST_SUITES += u-strbuf\n CLAR_TEST_SUITES += u-strcmp-offset\n@@ -1382,7 +1383,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-reftable-stack\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n \ndiff --git a/t/meson.build b/t/meson.build\nindex bd0292f0c7..7c305a90b5 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -15,6 +15,7 @@ clar_test_suites = [\n   'unit-tests/u-reftable-reader.c',\n   'unit-tests/u-reftable-readwrite.c',\n   'unit-tests/u-reftable-record.c',\n+  'unit-tests/u-reftable-stack.c',\n   'unit-tests/u-reftable-tree.c',\n   'unit-tests/u-strbuf.c',\n   'unit-tests/u-strcmp-offset.c',\n@@ -62,7 +63,6 @@ clar_unit_tests = executable('unit-tests',\n test('unit-tests', clar_unit_tests)\n \n unit_test_programs = [\n-  'unit-tests/t-reftable-stack.c',\n ]\n \n foreach unit_test_program : unit_test_programs\n@@ -1102,7 +1102,6 @@ integration_tests = [\n # sufficient to catch missing test suites in our CI though.\n foreach glob, tests : {\n   't[0-9][0-9][0-9][0-9]-*.sh': integration_tests,\n-  'unit-tests/t-*.c': unit_test_programs,\n   'unit-tests/u-*.c': clar_test_suites,\n }\n   actual_tests = run_command(shell, '-c', 'ls ' + glob,\ndiff --git a/t/unit-tests/t-reftable-stack.c b/t/unit-tests/t-reftable-stack.c\ndeleted file mode 100644\nindex c3f0059c34..0000000000\n--- a/t/unit-tests/t-reftable-stack.c\n+++ /dev/null\n@@ -1,1451 +0,0 @@\n-/*\n-Copyright 2020 Google LLC\n-\n-Use of this source code is governed by a BSD-style\n-license that can be found in the LICENSE file or at\n-https://developers.google.com/open-source/licenses/bsd\n-*/\n-\n-#define DISABLE_SIGN_COMPARE_WARNINGS\n-\n-#include \"test-lib.h\"\n-#include \"lib-reftable.h\"\n-#include \"dir.h\"\n-#include \"reftable/merged.h\"\n-#include \"reftable/reader.h\"\n-#include \"reftable/reftable-error.h\"\n-#include \"reftable/stack.h\"\n-#include \"strbuf.h\"\n-#include \"tempfile.h\"\n-#include <dirent.h>\n-\n-static void clear_dir(const char *dirname)\n-{\n-\tstruct strbuf path = REFTABLE_BUF_INIT;\n-\tstrbuf_addstr(&path, dirname);\n-\tremove_dir_recursively(&path, 0);\n-\tstrbuf_release(&path);\n-}\n-\n-static int count_dir_entries(const char *dirname)\n-{\n-\tDIR *dir = opendir(dirname);\n-\tint len = 0;\n-\tstruct dirent *d;\n-\tif (!dir)\n-\t\treturn 0;\n-\n-\twhile ((d = readdir(dir))) {\n-\t\t/*\n-\t\t * Besides skipping over \".\" and \"..\", we also need to\n-\t\t * skip over other files that have a leading \".\". This\n-\t\t * is due to behaviour of NFS, which will rename files\n-\t\t * to \".nfs*\" to emulate delete-on-last-close.\n-\t\t *\n-\t\t * In any case this should be fine as the reftable\n-\t\t * library will never write files with leading dots\n-\t\t * anyway.\n-\t\t */\n-\t\tif (starts_with(d->d_name, \".\"))\n-\t\t\tcontinue;\n-\t\tlen++;\n-\t}\n-\tclosedir(dir);\n-\treturn len;\n-}\n-\n-/*\n- * Work linenumber into the tempdir, so we can see which tests forget to\n- * cleanup.\n- */\n-static char *get_tmp_template(int linenumber)\n-{\n-\tconst char *tmp = getenv(\"TMPDIR\");\n-\tstatic char template[1024];\n-\tsnprintf(template, sizeof(template) - 1, \"%s/stack_test-%d.XXXXXX\",\n-\t\t tmp ? tmp : \"/tmp\", linenumber);\n-\treturn template;\n-}\n-\n-static char *get_tmp_dir(int linenumber)\n-{\n-\tchar *dir = get_tmp_template(linenumber);\n-\tcheck(mkdtemp(dir) != NULL);\n-\treturn dir;\n-}\n-\n-static void t_read_file(void)\n-{\n-\tchar *fn = get_tmp_template(__LINE__);\n-\tstruct tempfile *tmp = mks_tempfile(fn);\n-\tint fd = get_tempfile_fd(tmp);\n-\tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n-\tint n, err;\n-\tchar **names = NULL;\n-\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n-\n-\tcheck_int(fd, >, 0);\n-\tn = write_in_full(fd, out, strlen(out));\n-\tcheck_int(n, ==, strlen(out));\n-\terr = close(fd);\n-\tcheck_int(err, >=, 0);\n-\n-\terr = read_lines(fn, &names);\n-\tcheck(!err);\n-\n-\tfor (size_t i = 0; names[i]; i++)\n-\t\tcheck_str(want[i], names[i]);\n-\tfree_names(names);\n-\t(void) remove(fn);\n-\tdelete_tempfile(&tmp);\n-}\n-\n-static int write_test_ref(struct reftable_writer *wr, void *arg)\n-{\n-\tstruct reftable_ref_record *ref = arg;\n-\tcheck(!reftable_writer_set_limits(wr, ref->update_index,\n-\t\t\t\t\t  ref->update_index));\n-\treturn reftable_writer_add_ref(wr, ref);\n-}\n-\n-static void write_n_ref_tables(struct reftable_stack *st,\n-\t\t\t       size_t n)\n-{\n-\tint disable_auto_compact;\n-\tint err;\n-\n-\tdisable_auto_compact = st->opts.disable_auto_compact;\n-\tst->opts.disable_auto_compact = 1;\n-\n-\tfor (size_t i = 0; i < n; i++) {\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t};\n-\t\tchar buf[128];\n-\n-\t\tsnprintf(buf, sizeof(buf), \"refs/heads/branch-%04\"PRIuMAX, (uintmax_t)i);\n-\t\tref.refname = buf;\n-\t\tt_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);\n-\n-\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n-\t\tcheck(!err);\n-\t}\n-\n-\tst->opts.disable_auto_compact = disable_auto_compact;\n-}\n-\n-struct write_log_arg {\n-\tstruct reftable_log_record *log;\n-\tuint64_t update_index;\n-};\n-\n-static int write_test_log(struct reftable_writer *wr, void *arg)\n-{\n-\tstruct write_log_arg *wla = arg;\n-\n-\tcheck(!reftable_writer_set_limits(wr, wla->update_index,\n-\t\t\t\t\t  wla->update_index));\n-\treturn reftable_writer_add_log(wr, wla->log);\n-}\n-\n-static void t_reftable_stack_add_one(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n-\tint mask = umask(002);\n-\tstruct reftable_write_options opts = {\n-\t\t.default_permissions = 0660,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *) \"HEAD\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\tstruct reftable_ref_record dest = { 0 };\n-\tstruct stat stat_result = { 0 };\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st, write_test_ref, &ref);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n-\tcheck(!err);\n-\tcheck(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1));\n-\tcheck_int(st->readers_len, >, 0);\n-\n-#ifndef GIT_WINDOWS_NATIVE\n-\tcheck(!reftable_buf_addstr(&scratch, dir));\n-\tcheck(!reftable_buf_addstr(&scratch, \"/tables.list\"));\n-\terr = stat(scratch.buf, &stat_result);\n-\tcheck(!err);\n-\tcheck_int((stat_result.st_mode & 0777), ==, opts.default_permissions);\n-\n-\treftable_buf_reset(&scratch);\n-\tcheck(!reftable_buf_addstr(&scratch, dir));\n-\tcheck(!reftable_buf_addstr(&scratch, \"/\"));\n-\t/* do not try at home; not an external API for reftable. */\n-\tcheck(!reftable_buf_addstr(&scratch, st->readers[0]->name));\n-\terr = stat(scratch.buf, &stat_result);\n-\tcheck(!err);\n-\tcheck_int((stat_result.st_mode & 0777), ==, opts.default_permissions);\n-#else\n-\t(void) stat_result;\n-#endif\n-\n-\treftable_ref_record_release(&dest);\n-\treftable_stack_destroy(st);\n-\treftable_buf_release(&scratch);\n-\tclear_dir(dir);\n-\tumask(mask);\n-}\n-\n-static void t_reftable_stack_uptodate(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st1 = NULL;\n-\tstruct reftable_stack *st2 = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\n-\tint err;\n-\tstruct reftable_ref_record ref1 = {\n-\t\t.refname = (char *) \"HEAD\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\tstruct reftable_ref_record ref2 = {\n-\t\t.refname = (char *) \"branch2\",\n-\t\t.update_index = 2,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\n-\n-\t/* simulate multi-process access to the same stack\n-\t   by creating two stacks for the same directory.\n-\t */\n-\terr = reftable_new_stack(&st1, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_new_stack(&st2, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st1, write_test_ref, &ref1);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st2, write_test_ref, &ref2);\n-\tcheck_int(err, ==, REFTABLE_OUTDATED_ERROR);\n-\n-\terr = reftable_stack_reload(st2);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st2, write_test_ref, &ref2);\n-\tcheck(!err);\n-\treftable_stack_destroy(st1);\n-\treftable_stack_destroy(st2);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_transaction_api(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\tstruct reftable_addition *add = NULL;\n-\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *) \"HEAD\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\tstruct reftable_ref_record dest = { 0 };\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\treftable_addition_destroy(add);\n-\n-\terr = reftable_stack_new_addition(&add, st, 0);\n-\tcheck(!err);\n-\n-\terr = reftable_addition_add(add, write_test_ref, &ref);\n-\tcheck(!err);\n-\n-\terr = reftable_addition_commit(add);\n-\tcheck(!err);\n-\n-\treftable_addition_destroy(add);\n-\n-\terr = reftable_stack_read_ref(st, ref.refname, &dest);\n-\tcheck(!err);\n-\tcheck_int(REFTABLE_REF_SYMREF, ==, dest.value_type);\n-\tcheck(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1));\n-\n-\treftable_ref_record_release(&dest);\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_transaction_with_reload(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n-\tint err;\n-\tstruct reftable_addition *add = NULL;\n-\tstruct reftable_ref_record refs[2] = {\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/a\",\n-\t\t\t.update_index = 1,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { '1' },\n-\t\t},\n-\t\t{\n-\t\t\t.refname = (char *) \"refs/heads/b\",\n-\t\t\t.update_index = 2,\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t\t.value.val1 = { '1' },\n-\t\t},\n-\t};\n-\tstruct reftable_ref_record ref = { 0 };\n-\n-\terr = reftable_new_stack(&st1, dir, NULL);\n-\tcheck(!err);\n-\terr = reftable_new_stack(&st2, dir, NULL);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_new_addition(&add, st1, 0);\n-\tcheck(!err);\n-\terr = reftable_addition_add(add, write_test_ref, &refs[0]);\n-\tcheck(!err);\n-\terr = reftable_addition_commit(add);\n-\tcheck(!err);\n-\treftable_addition_destroy(add);\n-\n-\t/*\n-\t * The second stack is now outdated, which we should notice. We do not\n-\t * create the addition and lock the stack by default, but allow the\n-\t * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.\n-\t */\n-\terr = reftable_stack_new_addition(&add, st2, 0);\n-\tcheck_int(err, ==, REFTABLE_OUTDATED_ERROR);\n-\terr = reftable_stack_new_addition(&add, st2, REFTABLE_STACK_NEW_ADDITION_RELOAD);\n-\tcheck(!err);\n-\terr = reftable_addition_add(add, write_test_ref, &refs[1]);\n-\tcheck(!err);\n-\terr = reftable_addition_commit(add);\n-\tcheck(!err);\n-\treftable_addition_destroy(add);\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(refs); i++) {\n-\t\terr = reftable_stack_read_ref(st2, refs[i].refname, &ref);\n-\t\tcheck(!err);\n-\t\tcheck(reftable_ref_record_equal(&refs[i], &ref, REFTABLE_HASH_SIZE_SHA1));\n-\t}\n-\n-\treftable_ref_record_release(&ref);\n-\treftable_stack_destroy(st1);\n-\treftable_stack_destroy(st2);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_transaction_api_performs_auto_compaction(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = {0};\n-\tstruct reftable_addition *add = NULL;\n-\tstruct reftable_stack *st = NULL;\n-\tsize_t n = 20;\n-\tint err;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (size_t i = 0; i <= n; i++) {\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = (char *) \"master\",\n-\t\t};\n-\t\tchar name[100];\n-\n-\t\tsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n-\t\tref.refname = name;\n-\n-\t\t/*\n-\t\t * Disable auto-compaction for all but the last runs. Like this\n-\t\t * we can ensure that we indeed honor this setting and have\n-\t\t * better control over when exactly auto compaction runs.\n-\t\t */\n-\t\tst->opts.disable_auto_compact = i != n;\n-\n-\t\terr = reftable_stack_new_addition(&add, st, 0);\n-\t\tcheck(!err);\n-\n-\t\terr = reftable_addition_add(add, write_test_ref, &ref);\n-\t\tcheck(!err);\n-\n-\t\terr = reftable_addition_commit(add);\n-\t\tcheck(!err);\n-\n-\t\treftable_addition_destroy(add);\n-\n-\t\t/*\n-\t\t * The stack length should grow continuously for all runs where\n-\t\t * auto compaction is disabled. When enabled, we should merge\n-\t\t * all tables in the stack.\n-\t\t */\n-\t\tif (i != n)\n-\t\t\tcheck_int(st->merged->readers_len, ==, i + 1);\n-\t\telse\n-\t\t\tcheck_int(st->merged->readers_len, ==, 1);\n-\t}\n-\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_auto_compaction_fails_gracefully(void)\n-{\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *) \"refs/heads/master\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t.value.val1 = {0x01},\n-\t};\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st;\n-\tstruct reftable_buf table_path = REFTABLE_BUF_INIT;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st, write_test_ref, &ref);\n-\tcheck(!err);\n-\tcheck_int(st->merged->readers_len, ==, 1);\n-\tcheck_int(st->stats.attempts, ==, 0);\n-\tcheck_int(st->stats.failures, ==, 0);\n-\n-\t/*\n-\t * Lock the newly written table such that it cannot be compacted.\n-\t * Adding a new table to the stack should not be impacted by this, even\n-\t * though auto-compaction will now fail.\n-\t */\n-\tcheck(!reftable_buf_addstr(&table_path, dir));\n-\tcheck(!reftable_buf_addstr(&table_path, \"/\"));\n-\tcheck(!reftable_buf_addstr(&table_path, st->readers[0]->name));\n-\tcheck(!reftable_buf_addstr(&table_path, \".lock\"));\n-\twrite_file_buf(table_path.buf, \"\", 0);\n-\n-\tref.update_index = 2;\n-\terr = reftable_stack_add(st, write_test_ref, &ref);\n-\tcheck(!err);\n-\tcheck_int(st->merged->readers_len, ==, 2);\n-\tcheck_int(st->stats.attempts, ==, 1);\n-\tcheck_int(st->stats.failures, ==, 1);\n-\n-\treftable_stack_destroy(st);\n-\treftable_buf_release(&table_path);\n-\tclear_dir(dir);\n-}\n-\n-static int write_error(struct reftable_writer *wr UNUSED, void *arg)\n-{\n-\treturn *((int *)arg);\n-}\n-\n-static void t_reftable_stack_update_index_check(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\tstruct reftable_ref_record ref1 = {\n-\t\t.refname = (char *) \"name1\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\tstruct reftable_ref_record ref2 = {\n-\t\t.refname = (char *) \"name2\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st, write_test_ref, &ref1);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st, write_test_ref, &ref2);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_lock_failure(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tint err, i;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {\n-\t\terr = reftable_stack_add(st, write_error, &i);\n-\t\tcheck_int(err, ==, i);\n-\t}\n-\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_add(void)\n-{\n-\tint err = 0;\n-\tstruct reftable_write_options opts = {\n-\t\t.exact_log_message = 1,\n-\t\t.default_permissions = 0660,\n-\t\t.disable_auto_compact = 1,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_ref_record refs[2] = { 0 };\n-\tstruct reftable_log_record logs[2] = { 0 };\n-\tstruct reftable_buf path = REFTABLE_BUF_INIT;\n-\tstruct stat stat_result;\n-\tsize_t i, N = ARRAY_SIZE(refs);\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tchar buf[256];\n-\t\tsnprintf(buf, sizeof(buf), \"branch%02\"PRIuMAX, (uintmax_t)i);\n-\t\trefs[i].refname = xstrdup(buf);\n-\t\trefs[i].update_index = i + 1;\n-\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n-\t\tt_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);\n-\n-\t\tlogs[i].refname = xstrdup(buf);\n-\t\tlogs[i].update_index = N + i + 1;\n-\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n-\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n-\t\tt_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tint err = reftable_stack_add(st, write_test_ref, &refs[i]);\n-\t\tcheck(!err);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tstruct write_log_arg arg = {\n-\t\t\t.log = &logs[i],\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t};\n-\t\tint err = reftable_stack_add(st, write_test_log, &arg);\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_stack_compact_all(st, NULL);\n-\tcheck(!err);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tstruct reftable_ref_record dest = { 0 };\n-\n-\t\tint err = reftable_stack_read_ref(st, refs[i].refname, &dest);\n-\t\tcheck(!err);\n-\t\tcheck(reftable_ref_record_equal(&dest, refs + i,\n-\t\t\t\t\t\t REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_ref_record_release(&dest);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tstruct reftable_log_record dest = { 0 };\n-\t\tint err = reftable_stack_read_log(st, refs[i].refname, &dest);\n-\t\tcheck(!err);\n-\t\tcheck(reftable_log_record_equal(&dest, logs + i,\n-\t\t\t\t\t\t REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_log_record_release(&dest);\n-\t}\n-\n-#ifndef GIT_WINDOWS_NATIVE\n-\tcheck(!reftable_buf_addstr(&path, dir));\n-\tcheck(!reftable_buf_addstr(&path, \"/tables.list\"));\n-\terr = stat(path.buf, &stat_result);\n-\tcheck(!err);\n-\tcheck_int((stat_result.st_mode & 0777), ==, opts.default_permissions);\n-\n-\treftable_buf_reset(&path);\n-\tcheck(!reftable_buf_addstr(&path, dir));\n-\tcheck(!reftable_buf_addstr(&path, \"/\"));\n-\t/* do not try at home; not an external API for reftable. */\n-\tcheck(!reftable_buf_addstr(&path, st->readers[0]->name));\n-\terr = stat(path.buf, &stat_result);\n-\tcheck(!err);\n-\tcheck_int((stat_result.st_mode & 0777), ==, opts.default_permissions);\n-#else\n-\t(void) stat_result;\n-#endif\n-\n-\t/* cleanup */\n-\treftable_stack_destroy(st);\n-\tfor (i = 0; i < N; i++) {\n-\t\treftable_ref_record_release(&refs[i]);\n-\t\treftable_log_record_release(&logs[i]);\n-\t}\n-\treftable_buf_release(&path);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_iterator(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_ref_record refs[10] = { 0 };\n-\tstruct reftable_log_record logs[10] = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tsize_t N = ARRAY_SIZE(refs), i;\n-\tint err;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\trefs[i].refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n-\t\trefs[i].update_index = i + 1;\n-\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n-\t\tt_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);\n-\n-\t\tlogs[i].refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n-\t\tlogs[i].update_index = i + 1;\n-\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n-\t\tlogs[i].value.update.email = xstrdup(\"johndoe@invalid\");\n-\t\tlogs[i].value.update.message = xstrdup(\"commit\\n\");\n-\t\tt_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\terr = reftable_stack_add(st, write_test_ref, &refs[i]);\n-\t\tcheck(!err);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tstruct write_log_arg arg = {\n-\t\t\t.log = &logs[i],\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t};\n-\n-\t\terr = reftable_stack_add(st, write_test_log, &arg);\n-\t\tcheck(!err);\n-\t}\n-\n-\treftable_stack_init_ref_iterator(st, &it);\n-\treftable_iterator_seek_ref(&it, refs[0].refname);\n-\tfor (i = 0; ; i++) {\n-\t\tstruct reftable_ref_record ref = { 0 };\n-\n-\t\terr = reftable_iterator_next_ref(&it, &ref);\n-\t\tif (err > 0)\n-\t\t\tbreak;\n-\t\tcheck(!err);\n-\t\tcheck(reftable_ref_record_equal(&ref, &refs[i], REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_ref_record_release(&ref);\n-\t}\n-\tcheck_int(i, ==, N);\n-\n-\treftable_iterator_destroy(&it);\n-\n-\terr = reftable_stack_init_log_iterator(st, &it);\n-\tcheck(!err);\n-\n-\treftable_iterator_seek_log(&it, logs[0].refname);\n-\tfor (i = 0; ; i++) {\n-\t\tstruct reftable_log_record log = { 0 };\n-\n-\t\terr = reftable_iterator_next_log(&it, &log);\n-\t\tif (err > 0)\n-\t\t\tbreak;\n-\t\tcheck(!err);\n-\t\tcheck(reftable_log_record_equal(&log, &logs[i], REFTABLE_HASH_SIZE_SHA1));\n-\t\treftable_log_record_release(&log);\n-\t}\n-\tcheck_int(i, ==, N);\n-\n-\treftable_stack_destroy(st);\n-\treftable_iterator_destroy(&it);\n-\tfor (i = 0; i < N; i++) {\n-\t\treftable_ref_record_release(&refs[i]);\n-\t\treftable_log_record_release(&logs[i]);\n-\t}\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_log_normalize(void)\n-{\n-\tint err = 0;\n-\tstruct reftable_write_options opts = {\n-\t\t0,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_log_record input = {\n-\t\t.refname = (char *) \"branch\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_LOG_UPDATE,\n-\t\t.value = {\n-\t\t\t.update = {\n-\t\t\t\t.new_hash = { 1 },\n-\t\t\t\t.old_hash = { 2 },\n-\t\t\t},\n-\t\t},\n-\t};\n-\tstruct reftable_log_record dest = {\n-\t\t.update_index = 0,\n-\t};\n-\tstruct write_log_arg arg = {\n-\t\t.log = &input,\n-\t\t.update_index = 1,\n-\t};\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tinput.value.update.message = (char *) \"one\\ntwo\";\n-\terr = reftable_stack_add(st, write_test_log, &arg);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\tinput.value.update.message = (char *) \"one\";\n-\terr = reftable_stack_add(st, write_test_log, &arg);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_read_log(st, input.refname, &dest);\n-\tcheck(!err);\n-\tcheck_str(dest.value.update.message, \"one\\n\");\n-\n-\tinput.value.update.message = (char *) \"two\\n\";\n-\targ.update_index = 2;\n-\terr = reftable_stack_add(st, write_test_log, &arg);\n-\tcheck(!err);\n-\terr = reftable_stack_read_log(st, input.refname, &dest);\n-\tcheck(!err);\n-\tcheck_str(dest.value.update.message, \"two\\n\");\n-\n-\t/* cleanup */\n-\treftable_stack_destroy(st);\n-\treftable_log_record_release(&dest);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_tombstone(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\tstruct reftable_ref_record refs[2] = { 0 };\n-\tstruct reftable_log_record logs[2] = { 0 };\n-\tsize_t i, N = ARRAY_SIZE(refs);\n-\tstruct reftable_ref_record dest = { 0 };\n-\tstruct reftable_log_record log_dest = { 0 };\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\t/* even entries add the refs, odd entries delete them. */\n-\tfor (i = 0; i < N; i++) {\n-\t\tconst char *buf = \"branch\";\n-\t\trefs[i].refname = xstrdup(buf);\n-\t\trefs[i].update_index = i + 1;\n-\t\tif (i % 2 == 0) {\n-\t\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n-\t\t\tt_reftable_set_hash(refs[i].value.val1, i,\n-\t\t\t\t\t    REFTABLE_HASH_SHA1);\n-\t\t}\n-\n-\t\tlogs[i].refname = xstrdup(buf);\n-\t\t/*\n-\t\t * update_index is part of the key so should be constant.\n-\t\t * The value itself should be less than the writer's upper\n-\t\t * limit.\n-\t\t */\n-\t\tlogs[i].update_index = 1;\n-\t\tif (i % 2 == 0) {\n-\t\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n-\t\t\tt_reftable_set_hash(logs[i].value.update.new_hash, i,\n-\t\t\t\t\t    REFTABLE_HASH_SHA1);\n-\t\t\tlogs[i].value.update.email =\n-\t\t\t\txstrdup(\"identity@invalid\");\n-\t\t}\n-\t}\n-\tfor (i = 0; i < N; i++) {\n-\t\tint err = reftable_stack_add(st, write_test_ref, &refs[i]);\n-\t\tcheck(!err);\n-\t}\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tstruct write_log_arg arg = {\n-\t\t\t.log = &logs[i],\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t};\n-\t\tint err = reftable_stack_add(st, write_test_log, &arg);\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n-\tcheck_int(err, ==, 1);\n-\treftable_ref_record_release(&dest);\n-\n-\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n-\tcheck_int(err, ==, 1);\n-\treftable_log_record_release(&log_dest);\n-\n-\terr = reftable_stack_compact_all(st, NULL);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_read_ref(st, \"branch\", &dest);\n-\tcheck_int(err, ==, 1);\n-\n-\terr = reftable_stack_read_log(st, \"branch\", &log_dest);\n-\tcheck_int(err, ==, 1);\n-\treftable_ref_record_release(&dest);\n-\treftable_log_record_release(&log_dest);\n-\n-\t/* cleanup */\n-\treftable_stack_destroy(st);\n-\tfor (i = 0; i < N; i++) {\n-\t\treftable_ref_record_release(&refs[i]);\n-\t\treftable_log_record_release(&logs[i]);\n-\t}\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_hash_id(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *) \"master\",\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"target\",\n-\t\t.update_index = 1,\n-\t};\n-\tstruct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };\n-\tstruct reftable_stack *st32 = NULL;\n-\tstruct reftable_write_options opts_default = { 0 };\n-\tstruct reftable_stack *st_default = NULL;\n-\tstruct reftable_ref_record dest = { 0 };\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st, write_test_ref, &ref);\n-\tcheck(!err);\n-\n-\t/* can't read it with the wrong hash ID. */\n-\terr = reftable_new_stack(&st32, dir, &opts32);\n-\tcheck_int(err, ==, REFTABLE_FORMAT_ERROR);\n-\n-\t/* check that we can read it back with default opts too. */\n-\terr = reftable_new_stack(&st_default, dir, &opts_default);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_read_ref(st_default, \"master\", &dest);\n-\tcheck(!err);\n-\n-\tcheck(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1));\n-\treftable_ref_record_release(&dest);\n-\treftable_stack_destroy(st);\n-\treftable_stack_destroy(st_default);\n-\tclear_dir(dir);\n-}\n-\n-static void t_suggest_compaction_segment(void)\n-{\n-\tuint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };\n-\tstruct segment min =\n-\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);\n-\tcheck_int(min.start, ==, 1);\n-\tcheck_int(min.end, ==, 10);\n-}\n-\n-static void t_suggest_compaction_segment_nothing(void)\n-{\n-\tuint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };\n-\tstruct segment result =\n-\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);\n-\tcheck_int(result.start, ==, result.end);\n-}\n-\n-static void t_reflog_expire(void)\n-{\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tstruct reftable_log_record logs[20] = { 0 };\n-\tsize_t i, N = ARRAY_SIZE(logs) - 1;\n-\tint err;\n-\tstruct reftable_log_expiry_config expiry = {\n-\t\t.time = 10,\n-\t};\n-\tstruct reftable_log_record log = { 0 };\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (i = 1; i <= N; i++) {\n-\t\tchar buf[256];\n-\t\tsnprintf(buf, sizeof(buf), \"branch%02\"PRIuMAX, (uintmax_t)i);\n-\n-\t\tlogs[i].refname = xstrdup(buf);\n-\t\tlogs[i].update_index = i;\n-\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n-\t\tlogs[i].value.update.time = i;\n-\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n-\t\tt_reftable_set_hash(logs[i].value.update.new_hash, i,\n-\t\t\t\t    REFTABLE_HASH_SHA1);\n-\t}\n-\n-\tfor (i = 1; i <= N; i++) {\n-\t\tstruct write_log_arg arg = {\n-\t\t\t.log = &logs[i],\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t};\n-\t\tint err = reftable_stack_add(st, write_test_log, &arg);\n-\t\tcheck(!err);\n-\t}\n-\n-\terr = reftable_stack_compact_all(st, NULL);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_compact_all(st, &expiry);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_read_log(st, logs[9].refname, &log);\n-\tcheck_int(err, ==, 1);\n-\n-\terr = reftable_stack_read_log(st, logs[11].refname, &log);\n-\tcheck(!err);\n-\n-\texpiry.min_update_index = 15;\n-\terr = reftable_stack_compact_all(st, &expiry);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_read_log(st, logs[14].refname, &log);\n-\tcheck_int(err, ==, 1);\n-\n-\terr = reftable_stack_read_log(st, logs[16].refname, &log);\n-\tcheck(!err);\n-\n-\t/* cleanup */\n-\treftable_stack_destroy(st);\n-\tfor (i = 0; i <= N; i++)\n-\t\treftable_log_record_release(&logs[i]);\n-\tclear_dir(dir);\n-\treftable_log_record_release(&log);\n-}\n-\n-static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)\n-{\n-\tcheck(!reftable_writer_set_limits(wr, 1, 1));\n-\treturn 0;\n-}\n-\n-static void t_empty_add(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_stack *st2 = NULL;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_add(st, write_nothing, NULL);\n-\tcheck(!err);\n-\n-\terr = reftable_new_stack(&st2, dir, &opts);\n-\tcheck(!err);\n-\tclear_dir(dir);\n-\treftable_stack_destroy(st);\n-\treftable_stack_destroy(st2);\n-}\n-\n-static int fastlogN(uint64_t sz, uint64_t N)\n-{\n-\tint l = 0;\n-\tif (sz == 0)\n-\t\treturn 0;\n-\tfor (; sz; sz /= N)\n-\t\tl++;\n-\treturn l - 1;\n-}\n-\n-static void t_reftable_stack_auto_compaction(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.disable_auto_compact = 1,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\tsize_t i, N = 100;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (i = 0; i < N; i++) {\n-\t\tchar name[100];\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.refname = name,\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = (char *) \"master\",\n-\t\t};\n-\t\tsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n-\n-\t\terr = reftable_stack_add(st, write_test_ref, &ref);\n-\t\tcheck(!err);\n-\n-\t\terr = reftable_stack_auto_compact(st);\n-\t\tcheck(!err);\n-\t\tcheck(i < 2 || st->merged->readers_len < 2 * fastlogN(i, 2));\n-\t}\n-\n-\tcheck_int(reftable_stack_compaction_stats(st)->entries_written, <,\n-\t       (uint64_t)(N * fastlogN(N, 2)));\n-\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_auto_compaction_factor(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.auto_compaction_factor = 5,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\tsize_t N = 100;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (size_t i = 0; i < N; i++) {\n-\t\tchar name[20];\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.refname = name,\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t\t.value_type = REFTABLE_REF_VAL1,\n-\t\t};\n-\t\txsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n-\n-\t\terr = reftable_stack_add(st, &write_test_ref, &ref);\n-\t\tcheck(!err);\n-\n-\t\tcheck(i < 5 || st->merged->readers_len < 5 * fastlogN(i, 5));\n-\t}\n-\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_auto_compaction_with_locked_tables(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.disable_auto_compact = 1,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\twrite_n_ref_tables(st, 5);\n-\tcheck_int(st->merged->readers_len, ==, 5);\n-\n-\t/*\n-\t * Given that all tables we have written should be roughly the same\n-\t * size, we expect that auto-compaction will want to compact all of the\n-\t * tables. Locking any of the tables will keep it from doing so.\n-\t */\n-\tcheck(!reftable_buf_addstr(&buf, dir));\n-\tcheck(!reftable_buf_addstr(&buf, \"/\"));\n-\tcheck(!reftable_buf_addstr(&buf, st->readers[2]->name));\n-\tcheck(!reftable_buf_addstr(&buf, \".lock\"));\n-\twrite_file_buf(buf.buf, \"\", 0);\n-\n-\t/*\n-\t * When parts of the stack are locked, then auto-compaction does a best\n-\t * effort compaction of those tables which aren't locked. So while this\n-\t * would in theory compact all tables, due to the preexisting lock we\n-\t * only compact the newest two tables.\n-\t */\n-\terr = reftable_stack_auto_compact(st);\n-\tcheck(!err);\n-\tcheck_int(st->stats.failures, ==, 0);\n-\tcheck_int(st->merged->readers_len, ==, 4);\n-\n-\treftable_stack_destroy(st);\n-\treftable_buf_release(&buf);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_add_performs_auto_compaction(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\tsize_t i, n = 20;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\tfor (i = 0; i <= n; i++) {\n-\t\tstruct reftable_ref_record ref = {\n-\t\t\t.update_index = reftable_stack_next_update_index(st),\n-\t\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t\t.value.symref = (char *) \"master\",\n-\t\t};\n-\t\tchar buf[128];\n-\n-\t\t/*\n-\t\t * Disable auto-compaction for all but the last runs. Like this\n-\t\t * we can ensure that we indeed honor this setting and have\n-\t\t * better control over when exactly auto compaction runs.\n-\t\t */\n-\t\tst->opts.disable_auto_compact = i != n;\n-\n-\t\tsnprintf(buf, sizeof(buf), \"branch-%04\"PRIuMAX, (uintmax_t)i);\n-\t\tref.refname = buf;\n-\n-\t\terr = reftable_stack_add(st, write_test_ref, &ref);\n-\t\tcheck(!err);\n-\n-\t\t/*\n-\t\t * The stack length should grow continuously for all runs where\n-\t\t * auto compaction is disabled. When enabled, we should merge\n-\t\t * all tables in the stack.\n-\t\t */\n-\t\tif (i != n)\n-\t\t\tcheck_int(st->merged->readers_len, ==, i + 1);\n-\t\telse\n-\t\t\tcheck_int(st->merged->readers_len, ==, 1);\n-\t}\n-\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_compaction_with_locked_tables(void)\n-{\n-\tstruct reftable_write_options opts = {\n-\t\t.disable_auto_compact = 1,\n-\t};\n-\tstruct reftable_stack *st = NULL;\n-\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\twrite_n_ref_tables(st, 3);\n-\tcheck_int(st->merged->readers_len, ==, 3);\n-\n-\t/* Lock one of the tables that we're about to compact. */\n-\tcheck(!reftable_buf_addstr(&buf, dir));\n-\tcheck(!reftable_buf_addstr(&buf, \"/\"));\n-\tcheck(!reftable_buf_addstr(&buf, st->readers[1]->name));\n-\tcheck(!reftable_buf_addstr(&buf, \".lock\"));\n-\twrite_file_buf(buf.buf, \"\", 0);\n-\n-\t/*\n-\t * Compaction is expected to fail given that we were not able to\n-\t * compact all tables.\n-\t */\n-\terr = reftable_stack_compact_all(st, NULL);\n-\tcheck_int(err, ==, REFTABLE_LOCK_ERROR);\n-\tcheck_int(st->stats.failures, ==, 1);\n-\tcheck_int(st->merged->readers_len, ==, 3);\n-\n-\treftable_stack_destroy(st);\n-\treftable_buf_release(&buf);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_compaction_concurrent(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\terr = reftable_new_stack(&st1, dir, &opts);\n-\tcheck(!err);\n-\twrite_n_ref_tables(st1, 3);\n-\n-\terr = reftable_new_stack(&st2, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_compact_all(st1, NULL);\n-\tcheck(!err);\n-\n-\treftable_stack_destroy(st1);\n-\treftable_stack_destroy(st2);\n-\n-\tcheck_int(count_dir_entries(dir), ==, 2);\n-\tclear_dir(dir);\n-}\n-\n-static void unclean_stack_close(struct reftable_stack *st)\n-{\n-\t/* break abstraction boundary to simulate unclean shutdown. */\n-\tfor (size_t i = 0; i < st->readers_len; i++)\n-\t\treftable_reader_decref(st->readers[i]);\n-\tst->readers_len = 0;\n-\tREFTABLE_FREE_AND_NULL(st->readers);\n-}\n-\n-static void t_reftable_stack_compaction_concurrent_clean(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\terr = reftable_new_stack(&st1, dir, &opts);\n-\tcheck(!err);\n-\twrite_n_ref_tables(st1, 3);\n-\n-\terr = reftable_new_stack(&st2, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_compact_all(st1, NULL);\n-\tcheck(!err);\n-\n-\tunclean_stack_close(st1);\n-\tunclean_stack_close(st2);\n-\n-\terr = reftable_new_stack(&st3, dir, &opts);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_clean(st3);\n-\tcheck(!err);\n-\tcheck_int(count_dir_entries(dir), ==, 2);\n-\n-\treftable_stack_destroy(st1);\n-\treftable_stack_destroy(st2);\n-\treftable_stack_destroy(st3);\n-\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_read_across_reload(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n-\tstruct reftable_ref_record rec = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\t/* Create a first stack and set up an iterator for it. */\n-\terr = reftable_new_stack(&st1, dir, &opts);\n-\tcheck(!err);\n-\twrite_n_ref_tables(st1, 2);\n-\tcheck_int(st1->merged->readers_len, ==, 2);\n-\treftable_stack_init_ref_iterator(st1, &it);\n-\terr = reftable_iterator_seek_ref(&it, \"\");\n-\tcheck(!err);\n-\n-\t/* Set up a second stack for the same directory and compact it. */\n-\terr = reftable_new_stack(&st2, dir, &opts);\n-\tcheck(!err);\n-\tcheck_int(st2->merged->readers_len, ==, 2);\n-\terr = reftable_stack_compact_all(st2, NULL);\n-\tcheck(!err);\n-\tcheck_int(st2->merged->readers_len, ==, 1);\n-\n-\t/*\n-\t * Verify that we can continue to use the old iterator even after we\n-\t * have reloaded its stack.\n-\t */\n-\terr = reftable_stack_reload(st1);\n-\tcheck(!err);\n-\tcheck_int(st1->merged->readers_len, ==, 1);\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck(!err);\n-\tcheck_str(rec.refname, \"refs/heads/branch-0000\");\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck(!err);\n-\tcheck_str(rec.refname, \"refs/heads/branch-0001\");\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck_int(err, >, 0);\n-\n-\treftable_ref_record_release(&rec);\n-\treftable_iterator_destroy(&it);\n-\treftable_stack_destroy(st1);\n-\treftable_stack_destroy(st2);\n-\tclear_dir(dir);\n-}\n-\n-static void t_reftable_stack_reload_with_missing_table(void)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tstruct reftable_stack *st = NULL;\n-\tstruct reftable_ref_record rec = { 0 };\n-\tstruct reftable_iterator it = { 0 };\n-\tstruct reftable_buf table_path = REFTABLE_BUF_INIT, content = REFTABLE_BUF_INIT;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tint err;\n-\n-\t/* Create a first stack and set up an iterator for it. */\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\twrite_n_ref_tables(st, 2);\n-\tcheck_int(st->merged->readers_len, ==, 2);\n-\treftable_stack_init_ref_iterator(st, &it);\n-\terr = reftable_iterator_seek_ref(&it, \"\");\n-\tcheck(!err);\n-\n-\t/*\n-\t * Update the tables.list file with some garbage data, while reusing\n-\t * our old readers. This should trigger a partial reload of the stack,\n-\t * where we try to reuse our old readers.\n-\t*/\n-\tcheck(!reftable_buf_addstr(&content, st->readers[0]->name));\n-\tcheck(!reftable_buf_addstr(&content, \"\\n\"));\n-\tcheck(!reftable_buf_addstr(&content, st->readers[1]->name));\n-\tcheck(!reftable_buf_addstr(&content, \"\\n\"));\n-\tcheck(!reftable_buf_addstr(&content, \"garbage\\n\"));\n-\tcheck(!reftable_buf_addstr(&table_path, st->list_file));\n-\tcheck(!reftable_buf_addstr(&table_path, \".lock\"));\n-\twrite_file_buf(table_path.buf, content.buf, content.len);\n-\terr = rename(table_path.buf, st->list_file);\n-\tcheck(!err);\n-\n-\terr = reftable_stack_reload(st);\n-\tcheck_int(err, ==, -4);\n-\tcheck_int(st->merged->readers_len, ==, 2);\n-\n-\t/*\n-\t * Even though the reload has failed, we should be able to continue\n-\t * using the iterator.\n-\t*/\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck(!err);\n-\tcheck_str(rec.refname, \"refs/heads/branch-0000\");\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck(!err);\n-\tcheck_str(rec.refname, \"refs/heads/branch-0001\");\n-\terr = reftable_iterator_next_ref(&it, &rec);\n-\tcheck_int(err, >, 0);\n-\n-\treftable_ref_record_release(&rec);\n-\treftable_iterator_destroy(&it);\n-\treftable_stack_destroy(st);\n-\treftable_buf_release(&table_path);\n-\treftable_buf_release(&content);\n-\tclear_dir(dir);\n-}\n-\n-static int write_limits_after_ref(struct reftable_writer *wr, void *arg)\n-{\n-\tstruct reftable_ref_record *ref = arg;\n-\tcheck(!reftable_writer_set_limits(wr, ref->update_index, ref->update_index));\n-\tcheck(!reftable_writer_add_ref(wr, ref));\n-\treturn reftable_writer_set_limits(wr, ref->update_index, ref->update_index);\n-}\n-\n-static void t_reftable_invalid_limit_updates(void)\n-{\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *) \"HEAD\",\n-\t\t.update_index = 1,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *) \"master\",\n-\t};\n-\tstruct reftable_write_options opts = {\n-\t\t.default_permissions = 0660,\n-\t};\n-\tstruct reftable_addition *add = NULL;\n-\tchar *dir = get_tmp_dir(__LINE__);\n-\tstruct reftable_stack *st = NULL;\n-\tint err;\n-\n-\terr = reftable_new_stack(&st, dir, &opts);\n-\tcheck(!err);\n-\n-\treftable_addition_destroy(add);\n-\n-\terr = reftable_stack_new_addition(&add, st, 0);\n-\tcheck(!err);\n-\n-\t/*\n-\t * write_limits_after_ref also updates the update indexes after adding\n-\t * the record. This should cause an err to be returned, since the limits\n-\t * must be set at the start.\n-\t */\n-\terr = reftable_addition_add(add, write_limits_after_ref, &ref);\n-\tcheck_int(err, ==, REFTABLE_API_ERROR);\n-\n-\treftable_addition_destroy(add);\n-\treftable_stack_destroy(st);\n-\tclear_dir(dir);\n-}\n-\n-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)\n-{\n-\tTEST(t_empty_add(), \"empty addition to stack\");\n-\tTEST(t_read_file(), \"read_lines works\");\n-\tTEST(t_reflog_expire(), \"expire reflog entries\");\n-\tTEST(t_reftable_invalid_limit_updates(), \"prevent limit updates after adding records\");\n-\tTEST(t_reftable_stack_add(), \"add multiple refs and logs to stack\");\n-\tTEST(t_reftable_stack_add_one(), \"add a single ref record to stack\");\n-\tTEST(t_reftable_stack_add_performs_auto_compaction(), \"addition to stack triggers auto-compaction\");\n-\tTEST(t_reftable_stack_auto_compaction(), \"stack must form geometric sequence after compaction\");\n-\tTEST(t_reftable_stack_auto_compaction_factor(), \"auto-compaction with non-default geometric factor\");\n-\tTEST(t_reftable_stack_auto_compaction_fails_gracefully(), \"failure on auto-compaction\");\n-\tTEST(t_reftable_stack_auto_compaction_with_locked_tables(), \"auto compaction with locked tables\");\n-\tTEST(t_reftable_stack_compaction_concurrent(), \"compaction with concurrent stack\");\n-\tTEST(t_reftable_stack_compaction_concurrent_clean(), \"compaction with unclean stack shutdown\");\n-\tTEST(t_reftable_stack_compaction_with_locked_tables(), \"compaction with locked tables\");\n-\tTEST(t_reftable_stack_hash_id(), \"read stack with wrong hash ID\");\n-\tTEST(t_reftable_stack_iterator(), \"log and ref iterator for reftable stack\");\n-\tTEST(t_reftable_stack_lock_failure(), \"stack addition with lockfile failure\");\n-\tTEST(t_reftable_stack_log_normalize(), \"log messages should be normalized\");\n-\tTEST(t_reftable_stack_read_across_reload(), \"stack iterators work across reloads\");\n-\tTEST(t_reftable_stack_reload_with_missing_table(), \"stack iteration with garbage tables\");\n-\tTEST(t_reftable_stack_tombstone(), \"'tombstone' refs in stack\");\n-\tTEST(t_reftable_stack_transaction_api(), \"update transaction to stack\");\n-\tTEST(t_reftable_stack_transaction_with_reload(), \"transaction with reload\");\n-\tTEST(t_reftable_stack_transaction_api_performs_auto_compaction(), \"update transaction triggers auto-compaction\");\n-\tTEST(t_reftable_stack_update_index_check(), \"update transactions with equal update indices\");\n-\tTEST(t_reftable_stack_uptodate(), \"stack must be reloaded before ref update\");\n-\tTEST(t_suggest_compaction_segment(), \"suggest_compaction_segment with basic input\");\n-\tTEST(t_suggest_compaction_segment_nothing(), \"suggest_compaction_segment with pre-compacted input\");\n-\n-\treturn test_done();\n-}\ndiff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c\nnew file mode 100644\nindex 0000000000..1f41d8c4ca\n--- /dev/null\n+++ b/t/unit-tests/u-reftable-stack.c\n@@ -0,0 +1,1247 @@\n+/*\n+Copyright 2020 Google LLC\n+\n+Use of this source code is governed by a BSD-style\n+license that can be found in the LICENSE file or at\n+https://developers.google.com/open-source/licenses/bsd\n+*/\n+\n+#define DISABLE_SIGN_COMPARE_WARNINGS\n+\n+#include \"unit-test.h\"\n+#include \"lib-reftable.h\"\n+#include \"dir.h\"\n+#include \"reftable/merged.h\"\n+#include \"reftable/reader.h\"\n+#include \"reftable/reftable-error.h\"\n+#include \"reftable/stack.h\"\n+#include \"strbuf.h\"\n+#include \"tempfile.h\"\n+#include <dirent.h>\n+\n+static void clear_dir(const char *dirname)\n+{\n+\tstruct strbuf path = REFTABLE_BUF_INIT;\n+\tstrbuf_addstr(&path, dirname);\n+\tremove_dir_recursively(&path, 0);\n+\tstrbuf_release(&path);\n+}\n+\n+static int count_dir_entries(const char *dirname)\n+{\n+\tDIR *dir = opendir(dirname);\n+\tint len = 0;\n+\tstruct dirent *d;\n+\tif (!dir)\n+\t\treturn 0;\n+\n+\twhile ((d = readdir(dir))) {\n+\t\t/*\n+\t\t * Besides skipping over \".\" and \"..\", we also need to\n+\t\t * skip over other files that have a leading \".\". This\n+\t\t * is due to behaviour of NFS, which will rename files\n+\t\t * to \".nfs*\" to emulate delete-on-last-close.\n+\t\t *\n+\t\t * In any case this should be fine as the reftable\n+\t\t * library will never write files with leading dots\n+\t\t * anyway.\n+\t\t */\n+\t\tif (starts_with(d->d_name, \".\"))\n+\t\t\tcontinue;\n+\t\tlen++;\n+\t}\n+\tclosedir(dir);\n+\treturn len;\n+}\n+\n+/*\n+ * Work linenumber into the tempdir, so we can see which tests forget to\n+ * cleanup.\n+ */\n+static char *get_tmp_template(int linenumber)\n+{\n+\tconst char *tmp = getenv(\"TMPDIR\");\n+\tstatic char template[1024];\n+\tsnprintf(template, sizeof(template) - 1, \"%s/stack_test-%d.XXXXXX\",\n+\t\t tmp ? tmp : \"/tmp\", linenumber);\n+\treturn template;\n+}\n+\n+static char *get_tmp_dir(int linenumber)\n+{\n+\tchar *dir = get_tmp_template(linenumber);\n+\tcl_assert(mkdtemp(dir) != NULL);\n+\treturn dir;\n+}\n+\n+void test_reftable_stack__read_file(void)\n+{\n+\tchar *fn = get_tmp_template(__LINE__);\n+\tstruct tempfile *tmp = mks_tempfile(fn);\n+\tint fd = get_tempfile_fd(tmp);\n+\tchar out[1024] = \"line1\\n\\nline2\\nline3\";\n+\tint n, err;\n+\tchar **names = NULL;\n+\tconst char *want[] = { \"line1\", \"line2\", \"line3\" };\n+\n+\tcl_assert(fd > 0);\n+\tn = write_in_full(fd, out, strlen(out));\n+\tcl_assert_equal_i(n, strlen(out));\n+\terr = close(fd);\n+\tcl_assert(err >= 0);\n+\n+\terr = read_lines(fn, &names);\n+\tcl_assert(err == 0);\n+\n+\tfor (size_t i = 0; names[i]; i++)\n+\t\tcl_assert_equal_s(want[i], names[i]);\n+\tfree_names(names);\n+\t(void) remove(fn);\n+\tdelete_tempfile(&tmp);\n+}\n+\n+static int write_test_ref(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct reftable_ref_record *ref = arg;\n+\tcl_assert(reftable_writer_set_limits(wr, ref->update_index,\n+\t\t\t\t\t\t\t\t\t\t ref->update_index) == 0);\n+\treturn reftable_writer_add_ref(wr, ref);\n+}\n+\n+static void write_n_ref_tables(struct reftable_stack *st,\n+\t\t\t       size_t n)\n+{\n+\tint disable_auto_compact;\n+\n+\tdisable_auto_compact = st->opts.disable_auto_compact;\n+\tst->opts.disable_auto_compact = 1;\n+\n+\tfor (size_t i = 0; i < n; i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t};\n+\t\tchar buf[128];\n+\n+\t\tsnprintf(buf, sizeof(buf), \"refs/heads/branch-%04\"PRIuMAX, (uintmax_t)i);\n+\t\tref.refname = buf;\n+\t\tcl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);\n+\n+\t\tcl_assert(reftable_stack_add(st, &write_test_ref, &ref) == 0);\n+\t}\n+\n+\tst->opts.disable_auto_compact = disable_auto_compact;\n+}\n+\n+struct write_log_arg {\n+\tstruct reftable_log_record *log;\n+\tuint64_t update_index;\n+};\n+\n+static int write_test_log(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct write_log_arg *wla = arg;\n+\n+\tcl_assert(reftable_writer_set_limits(wr, wla->update_index,\n+\t\t\t\t\t\t\t\t\t\t wla->update_index) == 0);\n+\treturn reftable_writer_add_log(wr, wla->log);\n+}\n+\n+void test_reftable_stack__add_one(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_buf scratch = REFTABLE_BUF_INIT;\n+\tint mask = umask(002);\n+\tstruct reftable_write_options opts = {\n+\t\t.default_permissions = 0660,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *) \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { 0 };\n+\tstruct stat stat_result = { 0 };\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);\n+\tcl_assert(reftable_stack_read_ref(st, ref.refname, &dest) == 0);\n+\tcl_assert(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\tcl_assert(st->readers_len > 0);\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+\tcl_assert(reftable_buf_addstr(&scratch, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&scratch, \"/tables.list\") == 0);\n+\tcl_assert(stat(scratch.buf, &stat_result) == 0);\n+\tcl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);\n+\n+\treftable_buf_reset(&scratch);\n+\tcl_assert(reftable_buf_addstr(&scratch, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&scratch, \"/\") == 0);\n+\t/* do not try at home; not an external API for reftable. */\n+\tcl_assert(reftable_buf_addstr(&scratch, st->readers[0]->name) == 0);\n+\tcl_assert(stat(scratch.buf, &stat_result) == 0);\n+\tcl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);\n+#else\n+\t(void) stat_result;\n+#endif\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\treftable_buf_release(&scratch);\n+\tclear_dir(dir);\n+\tumask(mask);\n+}\n+\n+void test_reftable_stack__uptodate(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st1 = NULL;\n+\tstruct reftable_stack *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = (char *) \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = (char *) \"branch2\",\n+\t\t.update_index = 2,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\n+\n+\t/* simulate multi-process access to the same stack\n+\t   by creating two stacks for the same directory.\n+\t */\n+\tcl_assert(reftable_new_stack(&st1, dir, &opts) == 0);\n+\tcl_assert(reftable_new_stack(&st2, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_add(st1, write_test_ref, &ref1) == 0);\n+\tcl_assert_equal_i(reftable_stack_add(st2, write_test_ref, &ref2),\n+\t\t\t\t\t  REFTABLE_OUTDATED_ERROR);\n+\tcl_assert(reftable_stack_reload(st2) == 0);\n+\tcl_assert(reftable_stack_add(st2, write_test_ref, &ref2) == 0);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__transaction_api(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_addition *add = NULL;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *) \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\tstruct reftable_ref_record dest = { 0 };\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\treftable_addition_destroy(add);\n+\n+\tcl_assert(reftable_stack_new_addition(&add, st, 0) == 0);\n+\tcl_assert(reftable_addition_add(add, write_test_ref, &ref) == 0);\n+\tcl_assert(reftable_addition_commit(add) == 0);\n+\n+\treftable_addition_destroy(add);\n+\n+\tcl_assert(reftable_stack_read_ref(st, ref.refname, &dest) == 0);\n+\tcl_assert_equal_i(REFTABLE_REF_SYMREF, dest.value_type);\n+\tcl_assert(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__transaction_with_reload(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tstruct reftable_addition *add = NULL;\n+\tstruct reftable_ref_record refs[2] = {\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/a\",\n+\t\t\t.update_index = 1,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { '1' },\n+\t\t},\n+\t\t{\n+\t\t\t.refname = (char *) \"refs/heads/b\",\n+\t\t\t.update_index = 2,\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t\t.value.val1 = { '1' },\n+\t\t},\n+\t};\n+\tstruct reftable_ref_record ref = { 0 };\n+\n+\tcl_assert(reftable_new_stack(&st1, dir, NULL) == 0);\n+\tcl_assert(reftable_new_stack(&st2, dir, NULL) == 0);\n+\tcl_assert(reftable_stack_new_addition(&add, st1, 0) == 0);\n+\tcl_assert(reftable_addition_add(add, write_test_ref, &refs[0]) == 0);\n+\tcl_assert(reftable_addition_commit(add) == 0);\n+\treftable_addition_destroy(add);\n+\n+\t/*\n+\t * The second stack is now outdated, which we should notice. We do not\n+\t * create the addition and lock the stack by default, but allow the\n+\t * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.\n+\t */\n+\tcl_assert_equal_i(reftable_stack_new_addition(&add, st2, 0),\n+\t\t\t\t\t  REFTABLE_OUTDATED_ERROR);\n+\tcl_assert(reftable_stack_new_addition(&add, st2,\n+\t\t\t\t\t\t\t\t\t\t  REFTABLE_STACK_NEW_ADDITION_RELOAD) == 0);\n+\tcl_assert(reftable_addition_add(add, write_test_ref, &refs[1]) == 0);\n+\tcl_assert(reftable_addition_commit(add) == 0);\n+\treftable_addition_destroy(add);\n+\n+\tfor (size_t i = 0; i < ARRAY_SIZE(refs); i++) {\n+\t\tcl_assert(reftable_stack_read_ref(st2, refs[i].refname, &ref) == 0);\n+\t\tcl_assert(reftable_ref_record_equal(&refs[i], &ref,\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t}\n+\n+\treftable_ref_record_release(&ref);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__transaction_api_performs_auto_compaction(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = {0};\n+\tstruct reftable_addition *add = NULL;\n+\tstruct reftable_stack *st = NULL;\n+\tsize_t n = 20;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (size_t i = 0; i <= n; i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = (char *) \"master\",\n+\t\t};\n+\t\tchar name[100];\n+\n+\t\tsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n+\t\tref.refname = name;\n+\n+\t\t/*\n+\t\t * Disable auto-compaction for all but the last runs. Like this\n+\t\t * we can ensure that we indeed honor this setting and have\n+\t\t * better control over when exactly auto compaction runs.\n+\t\t */\n+\t\tst->opts.disable_auto_compact = i != n;\n+\n+\t\tcl_assert(reftable_stack_new_addition(&add, st, 0) == 0);\n+\t\tcl_assert(reftable_addition_add(add, write_test_ref, &ref) == 0);\n+\t\tcl_assert(reftable_addition_commit(add) == 0);\n+\n+\t\treftable_addition_destroy(add);\n+\n+\t\t/*\n+\t\t * The stack length should grow continuously for all runs where\n+\t\t * auto compaction is disabled. When enabled, we should merge\n+\t\t * all tables in the stack.\n+\t\t */\n+\t\tif (i != n)\n+\t\t\tcl_assert_equal_i(st->merged->readers_len, i + 1);\n+\t\telse\n+\t\t\tcl_assert_equal_i(st->merged->readers_len, 1);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__auto_compaction_fails_gracefully(void)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *) \"refs/heads/master\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t.value.val1 = {0x01},\n+\t};\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st;\n+\tstruct reftable_buf table_path = REFTABLE_BUF_INIT;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);\n+\tcl_assert_equal_i(st->merged->readers_len, 1);\n+\tcl_assert_equal_i(st->stats.attempts, 0);\n+\tcl_assert_equal_i(st->stats.failures, 0);\n+\n+\t/*\n+\t * Lock the newly written table such that it cannot be compacted.\n+\t * Adding a new table to the stack should not be impacted by this, even\n+\t * though auto-compaction will now fail.\n+\t */\n+\tcl_assert(reftable_buf_addstr(&table_path, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&table_path, \"/\") == 0);\n+\tcl_assert(reftable_buf_addstr(&table_path, st->readers[0]->name) == 0);\n+\tcl_assert(reftable_buf_addstr(&table_path, \".lock\") == 0);\n+\twrite_file_buf(table_path.buf, \"\", 0);\n+\n+\tref.update_index = 2;\n+\tcl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);\n+\tcl_assert_equal_i(st->merged->readers_len, 2);\n+\tcl_assert_equal_i(st->stats.attempts, 1);\n+\tcl_assert_equal_i(st->stats.failures, 1);\n+\n+\treftable_stack_destroy(st);\n+\treftable_buf_release(&table_path);\n+\tclear_dir(dir);\n+}\n+\n+static int write_error(struct reftable_writer *wr UNUSED, void *arg)\n+{\n+\treturn *((int *)arg);\n+}\n+\n+void test_reftable_stack__update_index_check(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_ref_record ref1 = {\n+\t\t.refname = (char *) \"name1\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\tstruct reftable_ref_record ref2 = {\n+\t\t.refname = (char *) \"name2\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_add(st, write_test_ref, &ref1) == 0);\n+\tcl_assert_equal_i(reftable_stack_add(st, write_test_ref,\n+\t\t\t\t\t\t\t\t\t\t &ref2), REFTABLE_API_ERROR);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__lock_failure(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tint i;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\tfor (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)\n+\t\tcl_assert_equal_i(reftable_stack_add(st, write_error, &i), i);\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__add(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.exact_log_message = 1,\n+\t\t.default_permissions = 0660,\n+\t\t.disable_auto_compact = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_ref_record refs[2] = { 0 };\n+\tstruct reftable_log_record logs[2] = { 0 };\n+\tstruct reftable_buf path = REFTABLE_BUF_INIT;\n+\tstruct stat stat_result;\n+\tsize_t i, N = ARRAY_SIZE(refs);\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02\"PRIuMAX, (uintmax_t)i);\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\tcl_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = N + i + 1;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tcl_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++)\n+\t\tcl_assert(reftable_stack_add(st, write_test_ref, &refs[i]) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tcl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);\n+\t}\n+\n+\tcl_assert(reftable_stack_compact_all(st, NULL) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_ref_record dest = { 0 };\n+\n+\t\tcl_assert(reftable_stack_read_ref(st, refs[i].refname, &dest) == 0);\n+\t\tcl_assert(reftable_ref_record_equal(&dest, refs + i,\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_ref_record_release(&dest);\n+\t}\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct reftable_log_record dest = { 0 };\n+\t\tcl_assert(reftable_stack_read_log(st, refs[i].refname, &dest) == 0);\n+\t\tcl_assert(reftable_log_record_equal(&dest, logs + i,\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_log_record_release(&dest);\n+\t}\n+\n+#ifndef GIT_WINDOWS_NATIVE\n+\tcl_assert(reftable_buf_addstr(&path, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&path, \"/tables.list\") == 0);\n+\tcl_assert(stat(path.buf, &stat_result) == 0);\n+\tcl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);\n+\n+\treftable_buf_reset(&path);\n+\tcl_assert(reftable_buf_addstr(&path, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&path, \"/\") == 0);\n+\t/* do not try at home; not an external API for reftable. */\n+\tcl_assert(reftable_buf_addstr(&path, st->readers[0]->name) == 0);\n+\tcl_assert(stat(path.buf, &stat_result) == 0);\n+\tcl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);\n+#else\n+\t(void) stat_result;\n+#endif\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\treftable_buf_release(&path);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__iterator(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_ref_record refs[10] = { 0 };\n+\tstruct reftable_log_record logs[10] = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tsize_t N = ARRAY_SIZE(refs), i;\n+\tint err;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\trefs[i].refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n+\t\trefs[i].update_index = i + 1;\n+\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\tcl_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);\n+\n+\t\tlogs[i].refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n+\t\tlogs[i].update_index = i + 1;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.email = xstrdup(\"johndoe@invalid\");\n+\t\tlogs[i].value.update.message = xstrdup(\"commit\\n\");\n+\t\tcl_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);\n+\t}\n+\n+\tfor (i = 0; i < N; i++)\n+\t\tcl_assert(reftable_stack_add(st, write_test_ref, &refs[i]) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\n+\t\tcl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);\n+\t}\n+\n+\treftable_stack_init_ref_iterator(st, &it);\n+\treftable_iterator_seek_ref(&it, refs[0].refname);\n+\tfor (i = 0; ; i++) {\n+\t\tstruct reftable_ref_record ref = { 0 };\n+\n+\t\terr = reftable_iterator_next_ref(&it, &ref);\n+\t\tif (err > 0)\n+\t\t\tbreak;\n+\t\tcl_assert(err == 0);\n+\t\tcl_assert(reftable_ref_record_equal(&ref, &refs[i],\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_ref_record_release(&ref);\n+\t}\n+\tcl_assert_equal_i(i, N);\n+\n+\treftable_iterator_destroy(&it);\n+\n+\tcl_assert(reftable_stack_init_log_iterator(st, &it) == 0);\n+\n+\treftable_iterator_seek_log(&it, logs[0].refname);\n+\tfor (i = 0; ; i++) {\n+\t\tstruct reftable_log_record log = { 0 };\n+\n+\t\terr = reftable_iterator_next_log(&it, &log);\n+\t\tif (err > 0)\n+\t\t\tbreak;\n+\t\tcl_assert(err == 0);\n+\t\tcl_assert(reftable_log_record_equal(&log, &logs[i],\n+\t\t\t\t\t\t\t\t\t\t\tREFTABLE_HASH_SIZE_SHA1) != 0);\n+\t\treftable_log_record_release(&log);\n+\t}\n+\tcl_assert_equal_i(i, N);\n+\n+\treftable_stack_destroy(st);\n+\treftable_iterator_destroy(&it);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__log_normalize(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t0,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_log_record input = {\n+\t\t.refname = (char *) \"branch\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_LOG_UPDATE,\n+\t\t.value = {\n+\t\t\t.update = {\n+\t\t\t\t.new_hash = { 1 },\n+\t\t\t\t.old_hash = { 2 },\n+\t\t\t},\n+\t\t},\n+\t};\n+\tstruct reftable_log_record dest = {\n+\t\t.update_index = 0,\n+\t};\n+\tstruct write_log_arg arg = {\n+\t\t.log = &input,\n+\t\t.update_index = 1,\n+\t};\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tinput.value.update.message = (char *) \"one\\ntwo\";\n+\tcl_assert_equal_i(reftable_stack_add(st, write_test_log,\n+\t\t\t\t\t\t\t\t\t\t &arg), REFTABLE_API_ERROR);\n+\n+\tinput.value.update.message = (char *) \"one\";\n+\tcl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);\n+\tcl_assert(reftable_stack_read_log(st, input.refname, &dest) == 0);\n+\tcl_assert_equal_s(dest.value.update.message, \"one\\n\");\n+\n+\tinput.value.update.message = (char *) \"two\\n\";\n+\targ.update_index = 2;\n+\tcl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);\n+\tcl_assert(reftable_stack_read_log(st, input.refname, &dest) == 0);\n+\tcl_assert_equal_s(dest.value.update.message, \"two\\n\");\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\treftable_log_record_release(&dest);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__tombstone(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_ref_record refs[2] = { 0 };\n+\tstruct reftable_log_record logs[2] = { 0 };\n+\tsize_t i, N = ARRAY_SIZE(refs);\n+\tstruct reftable_ref_record dest = { 0 };\n+\tstruct reftable_log_record log_dest = { 0 };\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\t/* even entries add the refs, odd entries delete them. */\n+\tfor (i = 0; i < N; i++) {\n+\t\tconst char *buf = \"branch\";\n+\t\trefs[i].refname = xstrdup(buf);\n+\t\trefs[i].update_index = i + 1;\n+\t\tif (i % 2 == 0) {\n+\t\t\trefs[i].value_type = REFTABLE_REF_VAL1;\n+\t\t\tcl_reftable_set_hash(refs[i].value.val1, i,\n+\t\t\t\t\t    REFTABLE_HASH_SHA1);\n+\t\t}\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\t/*\n+\t\t * update_index is part of the key so should be constant.\n+\t\t * The value itself should be less than the writer's upper\n+\t\t * limit.\n+\t\t */\n+\t\tlogs[i].update_index = 1;\n+\t\tif (i % 2 == 0) {\n+\t\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\t\tcl_reftable_set_hash(logs[i].value.update.new_hash, i,\n+\t\t\t\t\t    REFTABLE_HASH_SHA1);\n+\t\t\tlogs[i].value.update.email =\n+\t\t\t\txstrdup(\"identity@invalid\");\n+\t\t}\n+\t}\n+\tfor (i = 0; i < N; i++)\n+\t\tcl_assert(reftable_stack_add(st, write_test_ref, &refs[i]) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tcl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);\n+\t}\n+\n+\tcl_assert_equal_i(reftable_stack_read_ref(st, \"branch\", &dest), 1);\n+\treftable_ref_record_release(&dest);\n+\n+\tcl_assert_equal_i(reftable_stack_read_log(st, \"branch\", &log_dest), 1);\n+\treftable_log_record_release(&log_dest);\n+\n+\tcl_assert(reftable_stack_compact_all(st, NULL) == 0);\n+\tcl_assert_equal_i(reftable_stack_read_ref(st, \"branch\", &dest), 1);\n+\tcl_assert_equal_i(reftable_stack_read_log(st, \"branch\", &log_dest), 1);\n+\treftable_ref_record_release(&dest);\n+\treftable_log_record_release(&log_dest);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i < N; i++) {\n+\t\treftable_ref_record_release(&refs[i]);\n+\t\treftable_log_record_release(&logs[i]);\n+\t}\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__hash_id(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *) \"master\",\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"target\",\n+\t\t.update_index = 1,\n+\t};\n+\tstruct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };\n+\tstruct reftable_stack *st32 = NULL;\n+\tstruct reftable_write_options opts_default = { 0 };\n+\tstruct reftable_stack *st_default = NULL;\n+\tstruct reftable_ref_record dest = { 0 };\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);\n+\n+\t/* can't read it with the wrong hash ID. */\n+\tcl_assert_equal_i(reftable_new_stack(&st32, dir, &opts32), REFTABLE_FORMAT_ERROR);\n+\n+\t/* check that we can read it back with default opts too. */\n+\tcl_assert(reftable_new_stack(&st_default, dir, &opts_default) == 0);\n+\tcl_assert(reftable_stack_read_ref(st_default, \"master\", &dest) == 0);\n+\tcl_assert(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1) != 0);\n+\treftable_ref_record_release(&dest);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st_default);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__suggest_compaction_segment(void)\n+{\n+\tuint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };\n+\tstruct segment min =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);\n+\tcl_assert_equal_i(min.start, 1);\n+\tcl_assert_equal_i(min.end, 10);\n+}\n+\n+void test_reftable_stack__suggest_compaction_segment_nothing(void)\n+{\n+\tuint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };\n+\tstruct segment result =\n+\t\tsuggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);\n+\tcl_assert_equal_i(result.start, result.end);\n+}\n+\n+void test_reftable_stack__reflog_expire(void)\n+{\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_log_record logs[20] = { 0 };\n+\tsize_t i, N = ARRAY_SIZE(logs) - 1;\n+\tstruct reftable_log_expiry_config expiry = {\n+\t\t.time = 10,\n+\t};\n+\tstruct reftable_log_record log = { 0 };\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tchar buf[256];\n+\t\tsnprintf(buf, sizeof(buf), \"branch%02\"PRIuMAX, (uintmax_t)i);\n+\n+\t\tlogs[i].refname = xstrdup(buf);\n+\t\tlogs[i].update_index = i;\n+\t\tlogs[i].value_type = REFTABLE_LOG_UPDATE;\n+\t\tlogs[i].value.update.time = i;\n+\t\tlogs[i].value.update.email = xstrdup(\"identity@invalid\");\n+\t\tcl_reftable_set_hash(logs[i].value.update.new_hash, i,\n+\t\t\t\t    REFTABLE_HASH_SHA1);\n+\t}\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tstruct write_log_arg arg = {\n+\t\t\t.log = &logs[i],\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t};\n+\t\tcl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);\n+\t}\n+\n+\tcl_assert(reftable_stack_compact_all(st, NULL) == 0);\n+\tcl_assert(reftable_stack_compact_all(st, &expiry) == 0);\n+\tcl_assert_equal_i(reftable_stack_read_log(st, logs[9].refname, &log), 1);\n+\tcl_assert(reftable_stack_read_log(st, logs[11].refname, &log) == 0);\n+\n+\texpiry.min_update_index = 15;\n+\tcl_assert(reftable_stack_compact_all(st, &expiry) == 0);\n+\tcl_assert_equal_i(reftable_stack_read_log(st, logs[14].refname, &log), 1);\n+\tcl_assert(reftable_stack_read_log(st, logs[16].refname, &log) == 0);\n+\n+\t/* cleanup */\n+\treftable_stack_destroy(st);\n+\tfor (i = 0; i <= N; i++)\n+\t\treftable_log_record_release(&logs[i]);\n+\tclear_dir(dir);\n+\treftable_log_record_release(&log);\n+}\n+\n+static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)\n+{\n+\tcl_assert(reftable_writer_set_limits(wr, 1, 1) == 0);\n+\treturn 0;\n+}\n+\n+void test_reftable_stack__empty_add(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_stack *st2 = NULL;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_add(st, write_nothing, NULL) == 0);\n+\tcl_assert(reftable_new_stack(&st2, dir, &opts) == 0);\n+\tclear_dir(dir);\n+\treftable_stack_destroy(st);\n+\treftable_stack_destroy(st2);\n+}\n+\n+static int fastlogN(uint64_t sz, uint64_t N)\n+{\n+\tint l = 0;\n+\tif (sz == 0)\n+\t\treturn 0;\n+\tfor (; sz; sz /= N)\n+\t\tl++;\n+\treturn l - 1;\n+}\n+\n+void test_reftable_stack__auto_compaction(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.disable_auto_compact = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tsize_t i, N = 100;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (i = 0; i < N; i++) {\n+\t\tchar name[100];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = (char *) \"master\",\n+\t\t};\n+\t\tsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n+\n+\t\tcl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);\n+\t\tcl_assert(reftable_stack_auto_compact(st) == 0);\n+\t\tcl_assert(i < 2 || st->merged->readers_len < 2 * fastlogN(i, 2));\n+\t}\n+\n+\tcl_assert(reftable_stack_compaction_stats(st)->entries_written <\n+\t\t\t  (uint64_t)(N * fastlogN(N, 2)));\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__auto_compaction_factor(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.auto_compaction_factor = 5,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tsize_t N = 100;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (size_t i = 0; i < N; i++) {\n+\t\tchar name[20];\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.refname = name,\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_VAL1,\n+\t\t};\n+\t\txsnprintf(name, sizeof(name), \"branch%04\"PRIuMAX, (uintmax_t)i);\n+\n+\t\tcl_assert(reftable_stack_add(st, &write_test_ref, &ref) == 0);\n+\t\tcl_assert(i < 5 || st->merged->readers_len < 5 * fastlogN(i, 5));\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__auto_compaction_with_locked_tables(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.disable_auto_compact = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\twrite_n_ref_tables(st, 5);\n+\tcl_assert_equal_i(st->merged->readers_len, 5);\n+\n+\t/*\n+\t * Given that all tables we have written should be roughly the same\n+\t * size, we expect that auto-compaction will want to compact all of the\n+\t * tables. Locking any of the tables will keep it from doing so.\n+\t */\n+\tcl_assert(reftable_buf_addstr(&buf, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&buf, \"/\") == 0);\n+\tcl_assert(reftable_buf_addstr(&buf, st->readers[2]->name) == 0);\n+\tcl_assert(reftable_buf_addstr(&buf, \".lock\") == 0);\n+\twrite_file_buf(buf.buf, \"\", 0);\n+\n+\t/*\n+\t * When parts of the stack are locked, then auto-compaction does a best\n+\t * effort compaction of those tables which aren't locked. So while this\n+\t * would in theory compact all tables, due to the preexisting lock we\n+\t * only compact the newest two tables.\n+\t */\n+\tcl_assert(reftable_stack_auto_compact(st) == 0);\n+\tcl_assert_equal_i(st->stats.failures, 0);\n+\tcl_assert_equal_i(st->merged->readers_len, 4);\n+\n+\treftable_stack_destroy(st);\n+\treftable_buf_release(&buf);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__add_performs_auto_compaction(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tsize_t i, n = 20;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\tfor (i = 0; i <= n; i++) {\n+\t\tstruct reftable_ref_record ref = {\n+\t\t\t.update_index = reftable_stack_next_update_index(st),\n+\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t.value.symref = (char *) \"master\",\n+\t\t};\n+\t\tchar buf[128];\n+\n+\t\t/*\n+\t\t * Disable auto-compaction for all but the last runs. Like this\n+\t\t * we can ensure that we indeed honor this setting and have\n+\t\t * better control over when exactly auto compaction runs.\n+\t\t */\n+\t\tst->opts.disable_auto_compact = i != n;\n+\n+\t\tsnprintf(buf, sizeof(buf), \"branch-%04\"PRIuMAX, (uintmax_t)i);\n+\t\tref.refname = buf;\n+\n+\t\tcl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);\n+\n+\t\t/*\n+\t\t * The stack length should grow continuously for all runs where\n+\t\t * auto compaction is disabled. When enabled, we should merge\n+\t\t * all tables in the stack.\n+\t\t */\n+\t\tif (i != n)\n+\t\t\tcl_assert_equal_i(st->merged->readers_len, i + 1);\n+\t\telse\n+\t\t\tcl_assert_equal_i(st->merged->readers_len, 1);\n+\t}\n+\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__compaction_with_locked_tables(void)\n+{\n+\tstruct reftable_write_options opts = {\n+\t\t.disable_auto_compact = 1,\n+\t};\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_buf buf = REFTABLE_BUF_INIT;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\twrite_n_ref_tables(st, 3);\n+\tcl_assert_equal_i(st->merged->readers_len, 3);\n+\n+\t/* Lock one of the tables that we're about to compact. */\n+\tcl_assert(reftable_buf_addstr(&buf, dir) == 0);\n+\tcl_assert(reftable_buf_addstr(&buf, \"/\") == 0);\n+\tcl_assert(reftable_buf_addstr(&buf, st->readers[1]->name) == 0);\n+\tcl_assert(reftable_buf_addstr(&buf, \".lock\") == 0);\n+\twrite_file_buf(buf.buf, \"\", 0);\n+\n+\t/*\n+\t * Compaction is expected to fail given that we were not able to\n+\t * compact all tables.\n+\t */\n+\tcl_assert_equal_i(reftable_stack_compact_all(st, NULL), REFTABLE_LOCK_ERROR);\n+\tcl_assert_equal_i(st->stats.failures, 1);\n+\tcl_assert_equal_i(st->merged->readers_len, 3);\n+\n+\treftable_stack_destroy(st);\n+\treftable_buf_release(&buf);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__compaction_concurrent(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tcl_assert(reftable_new_stack(&st1, dir, &opts) == 0);\n+\twrite_n_ref_tables(st1, 3);\n+\n+\tcl_assert(reftable_new_stack(&st2, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_compact_all(st1, NULL) == 0);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\n+\tcl_assert_equal_i(count_dir_entries(dir), 2);\n+\tclear_dir(dir);\n+}\n+\n+static void unclean_stack_close(struct reftable_stack *st)\n+{\n+\t/* break abstraction boundary to simulate unclean shutdown. */\n+\tfor (size_t i = 0; i < st->readers_len; i++)\n+\t\treftable_reader_decref(st->readers[i]);\n+\tst->readers_len = 0;\n+\tREFTABLE_FREE_AND_NULL(st->readers);\n+}\n+\n+void test_reftable_stack__compaction_concurrent_clean(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\tcl_assert(reftable_new_stack(&st1, dir, &opts) == 0);\n+\twrite_n_ref_tables(st1, 3);\n+\n+\tcl_assert(reftable_new_stack(&st2, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_compact_all(st1, NULL) == 0);\n+\n+\tunclean_stack_close(st1);\n+\tunclean_stack_close(st2);\n+\n+\tcl_assert(reftable_new_stack(&st3, dir, &opts) == 0);\n+\tcl_assert(reftable_stack_clean(st3) == 0);\n+\tcl_assert_equal_i(count_dir_entries(dir), 2);\n+\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\treftable_stack_destroy(st3);\n+\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__read_across_reload(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st1 = NULL, *st2 = NULL;\n+\tstruct reftable_ref_record rec = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\t/* Create a first stack and set up an iterator for it. */\n+\tcl_assert(reftable_new_stack(&st1, dir, &opts) == 0);\n+\twrite_n_ref_tables(st1, 2);\n+\tcl_assert_equal_i(st1->merged->readers_len, 2);\n+\treftable_stack_init_ref_iterator(st1, &it);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\n+\t/* Set up a second stack for the same directory and compact it. */\n+\tcl_assert(reftable_new_stack(&st2, dir, &opts) == 0);\n+\tcl_assert_equal_i(st2->merged->readers_len, 2);\n+\tcl_assert(reftable_stack_compact_all(st2, NULL) == 0);\n+\tcl_assert_equal_i(st2->merged->readers_len, 1);\n+\n+\t/*\n+\t * Verify that we can continue to use the old iterator even after we\n+\t * have reloaded its stack.\n+\t */\n+\tcl_assert(reftable_stack_reload(st1) == 0);\n+\tcl_assert_equal_i(st1->merged->readers_len, 1);\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\tcl_assert_equal_s(rec.refname, \"refs/heads/branch-0000\");\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\tcl_assert_equal_s(rec.refname, \"refs/heads/branch-0001\");\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) > 0);\n+\n+\treftable_ref_record_release(&rec);\n+\treftable_iterator_destroy(&it);\n+\treftable_stack_destroy(st1);\n+\treftable_stack_destroy(st2);\n+\tclear_dir(dir);\n+}\n+\n+void test_reftable_stack__reload_with_missing_table(void)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tstruct reftable_stack *st = NULL;\n+\tstruct reftable_ref_record rec = { 0 };\n+\tstruct reftable_iterator it = { 0 };\n+\tstruct reftable_buf table_path = REFTABLE_BUF_INIT, content = REFTABLE_BUF_INIT;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\n+\t/* Create a first stack and set up an iterator for it. */\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\twrite_n_ref_tables(st, 2);\n+\tcl_assert_equal_i(st->merged->readers_len, 2);\n+\treftable_stack_init_ref_iterator(st, &it);\n+\tcl_assert(reftable_iterator_seek_ref(&it, \"\") == 0);\n+\n+\t/*\n+\t * Update the tables.list file with some garbage data, while reusing\n+\t * our old readers. This should trigger a partial reload of the stack,\n+\t * where we try to reuse our old readers.\n+\t*/\n+\tcl_assert(reftable_buf_addstr(&content, st->readers[0]->name) == 0);\n+\tcl_assert(reftable_buf_addstr(&content, \"\\n\") == 0);\n+\tcl_assert(reftable_buf_addstr(&content, st->readers[1]->name) == 0);\n+\tcl_assert(reftable_buf_addstr(&content, \"\\n\") == 0);\n+\tcl_assert(reftable_buf_addstr(&content, \"garbage\\n\") == 0);\n+\tcl_assert(reftable_buf_addstr(&table_path, st->list_file) == 0);\n+\tcl_assert(reftable_buf_addstr(&table_path, \".lock\") == 0);\n+\twrite_file_buf(table_path.buf, content.buf, content.len);\n+\tcl_assert(rename(table_path.buf, st->list_file) == 0);\n+\n+\tcl_assert_equal_i(reftable_stack_reload(st), -4);\n+\tcl_assert_equal_i(st->merged->readers_len, 2);\n+\n+\t/*\n+\t * Even though the reload has failed, we should be able to continue\n+\t * using the iterator.\n+\t*/\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\tcl_assert_equal_s(rec.refname, \"refs/heads/branch-0000\");\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) == 0);\n+\tcl_assert_equal_s(rec.refname, \"refs/heads/branch-0001\");\n+\tcl_assert(reftable_iterator_next_ref(&it, &rec) > 0);\n+\n+\treftable_ref_record_release(&rec);\n+\treftable_iterator_destroy(&it);\n+\treftable_stack_destroy(st);\n+\treftable_buf_release(&table_path);\n+\treftable_buf_release(&content);\n+\tclear_dir(dir);\n+}\n+\n+static int write_limits_after_ref(struct reftable_writer *wr, void *arg)\n+{\n+\tstruct reftable_ref_record *ref = arg;\n+\tcl_assert(reftable_writer_set_limits(wr, ref->update_index,\n+\t\t\t\t\t\t\t\t\t\t ref->update_index) == 0);\n+\tcl_assert(reftable_writer_add_ref(wr, ref) == 0);\n+\treturn reftable_writer_set_limits(wr, ref->update_index, ref->update_index);\n+}\n+\n+void test_reftable_stack__invalid_limit_updates(void)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *) \"HEAD\",\n+\t\t.update_index = 1,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *) \"master\",\n+\t};\n+\tstruct reftable_write_options opts = {\n+\t\t.default_permissions = 0660,\n+\t};\n+\tstruct reftable_addition *add = NULL;\n+\tchar *dir = get_tmp_dir(__LINE__);\n+\tstruct reftable_stack *st = NULL;\n+\n+\tcl_assert(reftable_new_stack(&st, dir, &opts) == 0);\n+\n+\treftable_addition_destroy(add);\n+\n+\tcl_assert(reftable_stack_new_addition(&add, st, 0) == 0);\n+\n+\t/*\n+\t * write_limits_after_ref also updates the update indexes after adding\n+\t * the record. This should cause an err to be returned, since the limits\n+\t * must be set at the start.\n+\t */\n+\tcl_assert_equal_i(reftable_addition_add(add, write_limits_after_ref, &ref),\n+\t\t\t\t\t  REFTABLE_API_ERROR);\n+\n+\treftable_addition_destroy(add);\n+\treftable_stack_destroy(st);\n+\tclear_dir(dir);\n+}\n-- \n2.43.0\n\n"},{"id":"516926","messageId":"xmqqa57zamuw.fsf@gitster.g","threadId":"63357","inReplyTo":"20250427185351.82520-2-kuforiji98@gmail.com","subject":"Re: [PATCH 1/9] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-04-28T23:27:03Z","receivedAt":"2025-04-28T23:27:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Seyi Kuforiji <kuforiji98@gmail.com> writes:\n\n> Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are\n> required for the reftable-related test files to run efficeintly. In the\n> current implementation these functions are designed to conform with our\n> homegrown unit-testing structure. So in other to convert the reftable\n> test files, there is need for a clar specific implementation of these\n> helper functions.\n>\n> type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++)`\n> Adapt functions in lib-reftable.{c,h} to use clar. These functions\n> conform with the clar testing framework and become available for all\n> reftable-related test files implemented using the clar testing\n> framework, which requires them. This will be used by subsequent commits.\n>\n> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n> ---\n>  Makefile                    |  4 ++--\n>  t/meson.build               |  4 ++--\n>  t/unit-tests/lib-reftable.c | 26 +++++++++++++-------------\n>  t/unit-tests/lib-reftable.h |  6 +++---\n>  4 files changed, 20 insertions(+), 20 deletions(-)\n\nWith this step (and nothing else, as this is the first patch in the\nseries) applied to 'master', I see tons of these errors:\n\n    CC t/unit-tests/t-reftable-merged.o\nt/unit-tests/t-reftable-merged.c: In function 'merged_table_from_records':\nt/unit-tests/t-reftable-merged.c:37:17: error: implicit declaration of function 't_reftable_write_to_buf'; did you mean 'cl_reftable_write_to_buf'? [-Wimplicit-function-declaration]\n   37 |                 t_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);\n      |                 ^~~~~~~~~~~~~~~~~~~~~~~\n      |                 cl_reftable_write_to_buf\n\n"},{"id":"516931","messageId":"CAGedMtfwA2vqOFxjLnusvFNcwKpTCLq38bZYBz-9cpzFmbhUdQ@mail.gmail.com","threadId":"63357","inReplyTo":"xmqqa57zamuw.fsf@gitster.g","subject":"Re: [PATCH 1/9] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Seyi Chamber","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T06:46:50Z","receivedAt":"2025-04-29T06:47:02Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"On Tue, 29 Apr 2025 at 00:27, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Seyi Kuforiji <kuforiji98@gmail.com> writes:\n>\n> > Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are\n> > required for the reftable-related test files to run efficeintly. In the\n> > current implementation these functions are designed to conform with our\n> > homegrown unit-testing structure. So in other to convert the reftable\n> > test files, there is need for a clar specific implementation of these\n> > helper functions.\n> >\n> > type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++)`\n> > Adapt functions in lib-reftable.{c,h} to use clar. These functions\n> > conform with the clar testing framework and become available for all\n> > reftable-related test files implemented using the clar testing\n> > framework, which requires them. This will be used by subsequent commits.\n> >\n> > Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n> > ---\n> >  Makefile                    |  4 ++--\n> >  t/meson.build               |  4 ++--\n> >  t/unit-tests/lib-reftable.c | 26 +++++++++++++-------------\n> >  t/unit-tests/lib-reftable.h |  6 +++---\n> >  4 files changed, 20 insertions(+), 20 deletions(-)\n>\n> With this step (and nothing else, as this is the first patch in the\n> series) applied to 'master', I see tons of these errors:\n>\n>     CC t/unit-tests/t-reftable-merged.o\n> t/unit-tests/t-reftable-merged.c: In function 'merged_table_from_records':\n> t/unit-tests/t-reftable-merged.c:37:17: error: implicit declaration of function 't_reftable_write_to_buf'; did you mean 'cl_reftable_write_to_buf'? [-Wimplicit-function-declaration]\n>    37 |                 t_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);\n>       |                 ^~~~~~~~~~~~~~~~~~~~~~~\n>       |                 cl_reftable_write_to_buf\n>\n\nHi Junio,\n\nYes, that is expected, as there are `reftable` test files that depend\non the old function names. I navigated this by temporarily porting the\nfunctions into the `t/unit-tests/unit-test{c,h}` file to convert the\nfiles and moved them back as soon as I was done converting them. Would\nit be better to leave them in `t/unit-tests/unit-test{c,h}` and then\ncreate a final patch that migrates them into the\n`t/unit-tests/lib-reftable{c,h} file?\n\nThanks\nSeyi\n"},{"id":"516932","messageId":"aBB6vHl5Gmtva7b3@pks.im","threadId":"63357","inReplyTo":"CAGedMtfwA2vqOFxjLnusvFNcwKpTCLq38bZYBz-9cpzFmbhUdQ@mail.gmail.com","subject":"Re: [PATCH 1/9] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-04-29T07:07:40Z","receivedAt":"2025-04-29T07:07:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 07:46:50AM +0100, Seyi Chamber wrote:\n> On Tue, 29 Apr 2025 at 00:27, Junio C Hamano <gitster@pobox.com> wrote:\n> > With this step (and nothing else, as this is the first patch in the\n> > series) applied to 'master', I see tons of these errors:\n> >\n> >     CC t/unit-tests/t-reftable-merged.o\n> > t/unit-tests/t-reftable-merged.c: In function 'merged_table_from_records':\n> > t/unit-tests/t-reftable-merged.c:37:17: error: implicit declaration of function 't_reftable_write_to_buf'; did you mean 'cl_reftable_write_to_buf'? [-Wimplicit-function-declaration]\n> >    37 |                 t_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);\n> >       |                 ^~~~~~~~~~~~~~~~~~~~~~~\n> >       |                 cl_reftable_write_to_buf\n> >\n> \n> Hi Junio,\n> \n> Yes, that is expected, as there are `reftable` test files that depend\n> on the old function names. I navigated this by temporarily porting the\n> functions into the `t/unit-tests/unit-test{c,h}` file to convert the\n> files and moved them back as soon as I was done converting them. Would\n> it be better to leave them in `t/unit-tests/unit-test{c,h}` and then\n> create a final patch that migrates them into the\n> `t/unit-tests/lib-reftable{c,h} file?\n\nWe do want the Git history to be fully bisectable so that it is easy for\ncontributors to find out where a specific bug has been introduced. And\nfor the history to be bisectable, we must ensure that every single\ncommit builds and passes our test suite. You can do that e.g. via `git\nrebase $BASE_COMMIT -x 'make -j$(nproc) test`, which builds and tests\nevery single commit in your commit series.\n\nSo yes, this should be adapted so that the initial commit introduces the\nnew helpers without removing the old helpers yet so that the unit tests\nthat haven't yet been converted continue to build and test just fine.\n\nThanks!\n\nPatrick\n"},{"id":"516959","messageId":"xmqqtt677z1r.fsf@gitster.g","threadId":"63357","inReplyTo":"aBB6vHl5Gmtva7b3@pks.im","subject":"Re: [PATCH 1/9] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-04-29T15:44:16Z","receivedAt":"2025-04-29T15:44:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We do want the Git history to be fully bisectable so that it is easy for\n> contributors to find out where a specific bug has been introduced. And\n> for the history to be bisectable, we must ensure that every single\n> commit builds and passes our test suite. You can do that e.g. via `git\n> rebase $BASE_COMMIT -x 'make -j$(nproc) test`, which builds and tests\n> every single commit in your commit series.\n>\n> So yes, this should be adapted so that the initial commit introduces the\n> new helpers without removing the old helpers yet so that the unit tests\n> that haven't yet been converted continue to build and test just fine.\n\nI started writing the same and thought somebody else may have\nalready responded, and found yours.  Very well said.\n\nThanks for spelling out the expectations we have in this project and\nexplaining how to achieve that goal.\n"}]}