{"thread":{"id":"63370","subject":"[PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar","startedAt":"2025-04-29T17:53:33Z","lastAt":"2025-05-26T12:56:22Z","messageCount":31,"participants":["Seyi Kuforiji","Junio C Hamano","Patrick Steinhardt","Seyi Chamber"],"isPatch":true,"patchVersion":2,"patchTotal":10},"messages":[{"id":"516969","messageId":"20250429175302.23724-1-kuforiji98@gmail.com","threadId":"63370","inReplyTo":null,"subject":"[PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:52Z","receivedAt":"2025-04-29T17:53:33Z","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\nChanges in v2:\n - some code refactoring based on review, which includes \n temporary relocation of reftable helper functions to \n t/unit-tests/unit-test.{c,h} files in the initial commit\n\nThanks\nSeyi\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n\nSeyi Kuforiji (10):\n  t/unit-tests: implement reftable test helper functions in\n    unit-test.{c,h}\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  t/unit-tests: adapt lib-reftable{c,h} helper functions to 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\nRange-diff against v1:\n -:  ---------- >  1:  d85c5694ca t/unit-tests: implement reftable test helper functions in unit-test.{c,h}\n 2:  4c5fc06603 !  2:  d24faa0176 t/unit-tests: convert reftable basics test to use clar test framework\n    @@ Makefile: CLAR_TEST_SUITES += u-oid-array\n      CLAR_TEST_SUITES += u-reftable-tree\n      CLAR_TEST_SUITES += u-strbuf\n      CLAR_TEST_SUITES += u-strcmp-offset\n    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-basics\n      UNIT_TEST_PROGRAMS += t-reftable-block\n 3:  c9bdff8290 !  3:  d9eb47645b t/unit-tests: convert reftable block test to use clar\n    @@ Makefile: CLAR_TEST_SUITES += u-oidmap\n      CLAR_TEST_SUITES += u-reftable-tree\n      CLAR_TEST_SUITES += u-strbuf\n      CLAR_TEST_SUITES += u-strcmp-offset\n    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-block\n      UNIT_TEST_PROGRAMS += t-reftable-merged\n 4:  60e2ff8bca !  4:  6ffc7cdfc6 t/unit-tests: convert reftable merged test to use clar\n    @@ Makefile: CLAR_TEST_SUITES += u-oidtree\n      CLAR_TEST_SUITES += u-reftable-tree\n      CLAR_TEST_SUITES += u-strbuf\n      CLAR_TEST_SUITES += u-strcmp-offset\n    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-merged\n      UNIT_TEST_PROGRAMS += t-reftable-pq\n 5:  b06520b033 !  5:  19b08968a0 t/unit-tests: convert reftable pq test to use clar\n    @@ Makefile: CLAR_TEST_SUITES += u-prio-queue\n      CLAR_TEST_SUITES += u-reftable-tree\n      CLAR_TEST_SUITES += u-strbuf\n      CLAR_TEST_SUITES += u-strcmp-offset\n    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-pq\n      UNIT_TEST_PROGRAMS += t-reftable-reader\n 6:  8b8ad57ff5 !  6:  b34d6714ed t/unit-tests: convert reftable reader test to use clar\n    @@ Makefile: 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    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-reader\n      UNIT_TEST_PROGRAMS += t-reftable-readwrite\n 7:  da73e8c85b !  7:  05c909ea94 t/unit-tests: convert reftable readwrite test to use clar\n    @@ Makefile: 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    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-readwrite\n      UNIT_TEST_PROGRAMS += t-reftable-record\n 8:  ac6fd74321 !  8:  1515cf15b7 t/unit-tests: convert reftable record test to use clar\n    @@ Makefile: 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    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \n     -UNIT_TEST_PROGRAMS += t-reftable-record\n      UNIT_TEST_PROGRAMS += t-reftable-stack\n 9:  5bc6a599c6 !  9:  dcbb2b6e56 t/unit-tests: convert reftable stack test to use clar\n    @@ Makefile: 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    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o\n    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n    +@@ Makefile: 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      \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    - \n    + UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o\n     \n      ## t/meson.build ##\n     @@ t/meson.build: clar_test_suites = [\n 1:  304d17fef8 ! 10:  79aa0145aa t/unit-tests: adapt lib-reftable{c,h} helper functions to clar\n    @@ Commit message\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    +    framework, which requires them. This change migrates the helper\n    +    functions back into `lib-reftable.{c,h}`.\n     \n         Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n     \n    @@ Makefile: CLAR_TEST_SUITES += u-urlmatch-normalization\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    -@@ Makefile: 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    @@ t/unit-tests/lib-reftable.h\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    +\n    + ## t/unit-tests/unit-test.c ##\n    +@@\n    + #include \"unit-test.h\"\n    + #include \"hex.h\"\n    + #include \"parse-options.h\"\n    +-#include \"reftable/constants.h\"\n    +-#include \"reftable/writer.h\"\n    + #include \"strbuf.h\"\n    + #include \"string-list.h\"\n    + #include \"strvec.h\"\n    + \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    +-\n    +-static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)\n    +-{\n    +-\tstrbuf_add(b, data, sz);\n    +-\treturn sz;\n    +-}\n    +-\n    +-static int strbuf_writer_flush(void *arg UNUSED)\n    +-{\n    +-\treturn 0;\n    +-}\n    +-\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    +-\tcl_assert(ret == 0);\n    +-\treturn writer;\n    +-}\n    +-\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    +-\t\t\t     size_t nlogs,\n    +-\t\t\t     struct reftable_write_options *_opts)\n    +-{\n    +-\tstruct reftable_write_options opts = { 0 };\n    +-\tconst struct reftable_stats *stats;\n    +-\tstruct reftable_writer *writer;\n    +-\tuint64_t min = 0xffffffff;\n    +-\tuint64_t max = 0;\n    +-\tint ret;\n    +-\n    +-\tif (_opts)\n    +-\t\topts = *_opts;\n    +-\n    +-\tfor (size_t i = 0; i < nrefs; i++) {\n    +-\t\tuint64_t ui = refs[i].update_index;\n    +-\t\tif (ui > max)\n    +-\t\t\tmax = ui;\n    +-\t\tif (ui < min)\n    +-\t\t\tmin = ui;\n    +-\t}\n    +-\tfor (size_t i = 0; i < nlogs; i++) {\n    +-\t\tuint64_t ui = logs[i].update_index;\n    +-\t\tif (ui > max)\n    +-\t\t\tmax = ui;\n    +-\t\tif (ui < min)\n    +-\t\t\tmin = ui;\n    +-\t}\n    +-\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\tcl_assert_equal_i(ret, 0);\n    +-\t}\n    +-\n    +-\tif (nlogs) {\n    +-\t\tret = reftable_writer_add_logs(writer, logs, nlogs);\n    +-\t\tcl_assert_equal_i(ret, 0);\n    +-\t}\n    +-\n    +-\tret = reftable_writer_close(writer);\n    +-\tcl_assert_equal_i(ret, 0);\n    +-\n    +-\tstats = reftable_writer_stats(writer);\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\tcl_assert(buf->buf[off] == 'r');\n    +-\t}\n    +-\n    +-\tif (nrefs)\n    +-\t\tcl_assert(stats->ref_stats.blocks > 0);\n    +-\tif (nlogs)\n    +-\t\tcl_assert(stats->log_stats.blocks > 0);\n    +-\n    +-\treftable_writer_free(writer);\n    +-}\n    +-\n    + static const char * const unit_test_usage[] = {\n    + \tN_(\"unit-test [<options>]\"),\n    + \tNULL,\n    +\n    + ## t/unit-tests/unit-test.h ##\n    +@@\n    + #include \"git-compat-util.h\"\n    + #include \"clar/clar.h\"\n    + #include \"clar-decls.h\"\n    +-#include \"git-compat-util.h\"\n    +-#include \"reftable/reftable-writer.h\"\n    + #include \"strbuf.h\"\n    + \n    +-struct reftable_buf;\n    +-\n    +-void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);\n    +-\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 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    +-\t\t\t     size_t nlogs,\n    +-\t\t\t     struct reftable_write_options *opts);\n    +-\n    + #define cl_failf(fmt, ...) do { \\\n    + \tchar desc[4096]; \\\n    + \tsnprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \\\n-- \n2.43.0\n\n"},{"id":"516970","messageId":"20250429175302.23724-2-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:53Z","receivedAt":"2025-04-29T17:53:37Z","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;\ni++)`, implement equivalent helper functions in unit-test.{c,h} to use\nclar. These functions conform with the clar testing framework and become\navailable for all reftable-related test files implemented using the clar\ntesting framework, which requires them. This will be used by subsequent\ncommits.\n\nSigned-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n---\n t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++\n t/unit-tests/unit-test.h | 16 +++++++\n 2 files changed, 109 insertions(+)\n\ndiff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c\nindex 5af645048a..6c2a4e6aa8 100644\n--- a/t/unit-tests/unit-test.c\n+++ b/t/unit-tests/unit-test.c\n@@ -1,10 +1,103 @@\n #include \"unit-test.h\"\n #include \"hex.h\"\n #include \"parse-options.h\"\n+#include \"reftable/constants.h\"\n+#include \"reftable/writer.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"strvec.h\"\n \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+\n+static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)\n+{\n+\tstrbuf_add(b, data, sz);\n+\treturn sz;\n+}\n+\n+static int strbuf_writer_flush(void *arg UNUSED)\n+{\n+\treturn 0;\n+}\n+\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+\tcl_assert(ret == 0);\n+\treturn writer;\n+}\n+\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+\t\t\t     size_t nlogs,\n+\t\t\t     struct reftable_write_options *_opts)\n+{\n+\tstruct reftable_write_options opts = { 0 };\n+\tconst struct reftable_stats *stats;\n+\tstruct reftable_writer *writer;\n+\tuint64_t min = 0xffffffff;\n+\tuint64_t max = 0;\n+\tint ret;\n+\n+\tif (_opts)\n+\t\topts = *_opts;\n+\n+\tfor (size_t i = 0; i < nrefs; i++) {\n+\t\tuint64_t ui = refs[i].update_index;\n+\t\tif (ui > max)\n+\t\t\tmax = ui;\n+\t\tif (ui < min)\n+\t\t\tmin = ui;\n+\t}\n+\tfor (size_t i = 0; i < nlogs; i++) {\n+\t\tuint64_t ui = logs[i].update_index;\n+\t\tif (ui > max)\n+\t\t\tmax = ui;\n+\t\tif (ui < min)\n+\t\t\tmin = ui;\n+\t}\n+\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\tcl_assert_equal_i(ret, 0);\n+\t}\n+\n+\tif (nlogs) {\n+\t\tret = reftable_writer_add_logs(writer, logs, nlogs);\n+\t\tcl_assert_equal_i(ret, 0);\n+\t}\n+\n+\tret = reftable_writer_close(writer);\n+\tcl_assert_equal_i(ret, 0);\n+\n+\tstats = reftable_writer_stats(writer);\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\tcl_assert(buf->buf[off] == 'r');\n+\t}\n+\n+\tif (nrefs)\n+\t\tcl_assert(stats->ref_stats.blocks > 0);\n+\tif (nlogs)\n+\t\tcl_assert(stats->log_stats.blocks > 0);\n+\n+\treftable_writer_free(writer);\n+}\n+\n static const char * const unit_test_usage[] = {\n \tN_(\"unit-test [<options>]\"),\n \tNULL,\ndiff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h\nindex 85e5d6a948..fe0aebd876 100644\n--- a/t/unit-tests/unit-test.h\n+++ b/t/unit-tests/unit-test.h\n@@ -1,8 +1,24 @@\n #include \"git-compat-util.h\"\n #include \"clar/clar.h\"\n #include \"clar-decls.h\"\n+#include \"git-compat-util.h\"\n+#include \"reftable/reftable-writer.h\"\n #include \"strbuf.h\"\n \n+struct reftable_buf;\n+\n+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);\n+\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 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+\t\t\t     size_t nlogs,\n+\t\t\t     struct reftable_write_options *opts);\n+\n #define cl_failf(fmt, ...) do { \\\n \tchar desc[4096]; \\\n \tsnprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \\\n-- \n2.43.0\n\n"},{"id":"516971","messageId":"20250429175302.23724-3-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:54Z","receivedAt":"2025-04-29T17:53:41Z","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 13f9062a05..7b12bb078c 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@@ -1374,7 +1375,6 @@ 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 \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 bfb744e886..8a42b595d9 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@@ -54,7 +55,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":"516972","messageId":"20250429175302.23724-4-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:55Z","receivedAt":"2025-04-29T17:53:45Z","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 7b12bb078c..239c575dad 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@@ -1375,7 +1376,6 @@ 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 \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 8a42b595d9..e0d723e74b 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@@ -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-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":"516973","messageId":"20250429175302.23724-5-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 04/10] t/unit-tests: convert reftable merged test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:56Z","receivedAt":"2025-04-29T17:53: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 239c575dad..2b1642465a 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@@ -1376,7 +1377,6 @@ 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 \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 e0d723e74b..70a783ba80 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@@ -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-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":"516974","messageId":"20250429175302.23724-6-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 05/10] t/unit-tests: convert reftable pq test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:57Z","receivedAt":"2025-04-29T17:53:53Z","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 2b1642465a..4142927d0a 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@@ -1377,7 +1378,6 @@ 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 \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 70a783ba80..9bded2d15c 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@@ -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-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":"516975","messageId":"20250429175302.23724-7-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 06/10] t/unit-tests: convert reftable reader test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:58Z","receivedAt":"2025-04-29T17:53:57Z","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 4142927d0a..d3e8677653 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@@ -1378,7 +1379,6 @@ 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 \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 9bded2d15c..6a22bd2790 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@@ -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-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":"516976","messageId":"20250429175302.23724-8-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 07/10] t/unit-tests: convert reftable readwrite test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:52:59Z","receivedAt":"2025-04-29T17:54:01Z","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 d3e8677653..88b6851b37 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@@ -1379,7 +1380,6 @@ 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 \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 6a22bd2790..7722d177e2 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@@ -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-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":"516977","messageId":"20250429175302.23724-9-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 08/10] t/unit-tests: convert reftable record test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:53:00Z","receivedAt":"2025-04-29T17:54:05Z","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 88b6851b37..cd9db9adf1 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@@ -1380,7 +1381,6 @@ 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 \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 7722d177e2..756cb2a2dd 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@@ -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-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":"516979","messageId":"20250429175302.23724-10-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:53:01Z","receivedAt":"2025-04-29T17:54:21Z","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 cd9db9adf1..0b42893611 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@@ -1381,7 +1382,6 @@ 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 \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\ndiff --git a/t/meson.build b/t/meson.build\nindex 756cb2a2dd..8fa00fc9ef 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@@ -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-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":"516978","messageId":"20250429175302.23724-11-kuforiji98@gmail.com","threadId":"63370","inReplyTo":"20250429175302.23724-1-kuforiji98@gmail.com","subject":"[PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Seyi Kuforiji","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-04-29T17:53:02Z","receivedAt":"2025-04-29T17:54:24Z","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 change migrates the helper\nfunctions back into `lib-reftable.{c,h}`.\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 t/unit-tests/unit-test.c    | 93 -------------------------------------\n t/unit-tests/unit-test.h    | 16 -------\n 6 files changed, 20 insertions(+), 129 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 0b42893611..7e646e16ee 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1379,12 +1379,12 @@ 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_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 8fa00fc9ef..7c305a90b5 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -26,8 +26,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,\ndiff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c\nindex 6c2a4e6aa8..5af645048a 100644\n--- a/t/unit-tests/unit-test.c\n+++ b/t/unit-tests/unit-test.c\n@@ -1,103 +1,10 @@\n #include \"unit-test.h\"\n #include \"hex.h\"\n #include \"parse-options.h\"\n-#include \"reftable/constants.h\"\n-#include \"reftable/writer.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"strvec.h\"\n \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-\n-static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)\n-{\n-\tstrbuf_add(b, data, sz);\n-\treturn sz;\n-}\n-\n-static int strbuf_writer_flush(void *arg UNUSED)\n-{\n-\treturn 0;\n-}\n-\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-\tcl_assert(ret == 0);\n-\treturn writer;\n-}\n-\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-\t\t\t     size_t nlogs,\n-\t\t\t     struct reftable_write_options *_opts)\n-{\n-\tstruct reftable_write_options opts = { 0 };\n-\tconst struct reftable_stats *stats;\n-\tstruct reftable_writer *writer;\n-\tuint64_t min = 0xffffffff;\n-\tuint64_t max = 0;\n-\tint ret;\n-\n-\tif (_opts)\n-\t\topts = *_opts;\n-\n-\tfor (size_t i = 0; i < nrefs; i++) {\n-\t\tuint64_t ui = refs[i].update_index;\n-\t\tif (ui > max)\n-\t\t\tmax = ui;\n-\t\tif (ui < min)\n-\t\t\tmin = ui;\n-\t}\n-\tfor (size_t i = 0; i < nlogs; i++) {\n-\t\tuint64_t ui = logs[i].update_index;\n-\t\tif (ui > max)\n-\t\t\tmax = ui;\n-\t\tif (ui < min)\n-\t\t\tmin = ui;\n-\t}\n-\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\tcl_assert_equal_i(ret, 0);\n-\t}\n-\n-\tif (nlogs) {\n-\t\tret = reftable_writer_add_logs(writer, logs, nlogs);\n-\t\tcl_assert_equal_i(ret, 0);\n-\t}\n-\n-\tret = reftable_writer_close(writer);\n-\tcl_assert_equal_i(ret, 0);\n-\n-\tstats = reftable_writer_stats(writer);\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\tcl_assert(buf->buf[off] == 'r');\n-\t}\n-\n-\tif (nrefs)\n-\t\tcl_assert(stats->ref_stats.blocks > 0);\n-\tif (nlogs)\n-\t\tcl_assert(stats->log_stats.blocks > 0);\n-\n-\treftable_writer_free(writer);\n-}\n-\n static const char * const unit_test_usage[] = {\n \tN_(\"unit-test [<options>]\"),\n \tNULL,\ndiff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h\nindex fe0aebd876..85e5d6a948 100644\n--- a/t/unit-tests/unit-test.h\n+++ b/t/unit-tests/unit-test.h\n@@ -1,24 +1,8 @@\n #include \"git-compat-util.h\"\n #include \"clar/clar.h\"\n #include \"clar-decls.h\"\n-#include \"git-compat-util.h\"\n-#include \"reftable/reftable-writer.h\"\n #include \"strbuf.h\"\n \n-struct reftable_buf;\n-\n-void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);\n-\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 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-\t\t\t     size_t nlogs,\n-\t\t\t     struct reftable_write_options *opts);\n-\n #define cl_failf(fmt, ...) do { \\\n \tchar desc[4096]; \\\n \tsnprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \\\n-- \n2.43.0\n\n"},{"id":"516993","messageId":"xmqqbjse4ljd.fsf@gitster.g","threadId":"63370","inReplyTo":"20250429175302.23724-2-kuforiji98@gmail.com","subject":"Re: [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-04-29T23:04:22Z","receivedAt":"2025-04-29T23:04:26Z","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\nefficeintly?  effectively?  efficiently?  correctly?  Wouldn't it be\nsufficient to say \"... to run.\" without anything else?\n\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\nOK.\n\n> type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks;\n> i++)`, implement equivalent helper functions in unit-test.{c,h} to use\n> clar.\n\nI cannot quite parse this.\n\n> These functions conform with the clar testing framework and become\n> available for all reftable-related test files implemented using the clar\n> testing framework, which requires them. This will be used by subsequent\n> commits.\n\nOK.\n\n> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n> ---\n>  t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++\n>  t/unit-tests/unit-test.h | 16 +++++++\n>  2 files changed, 109 insertions(+)\n\nHmph, this probably is a question better asked to Patrick, but it\nsomehow feels a bit unsatisfactory that we are duplicating instead\nof replacing, as we cannot see \"ah, this removed thing is now added\nin a different shape to fit in the other framework\" in the patch---\ninstead what we see in the patch is a bunch of \"a completely new\nthing that honors the convention we are familiar in existing clar\nbased tests is added here\".\n\n"},{"id":"517104","messageId":"aBSXBv0oPOXpfC2i@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-10-kuforiji98@gmail.com","subject":"Re: [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:26Z","receivedAt":"2025-05-02T09:57:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:53:01PM +0100, Seyi Kuforiji wrote:\n> diff --git a/t/meson.build b/t/meson.build\n> index 756cb2a2dd..8fa00fc9ef 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\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,\n\nOkay. Ideally this would be moved into the next commit where we remove\nthe infrastructure for our old-style unit tests, but we can't because\nthe glob matches nothing anymore and thus causes an error.\n\nAlso, we have a \"check-meson\" target in \"t/Makefile\". Don't we have to\nremove unit tests from there, too?\n\nPatrick\n"},{"id":"517105","messageId":"aBSXDQ5BjUfG-GKZ@pks.im","threadId":"63370","inReplyTo":"xmqqbjse4ljd.fsf@gitster.g","subject":"Re: [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:33Z","receivedAt":"2025-05-02T09:57:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 04:04:22PM -0700, Junio C Hamano wrote:\n> Seyi Kuforiji <kuforiji98@gmail.com> writes:\n> > Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n> > ---\n> >  t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++\n> >  t/unit-tests/unit-test.h | 16 +++++++\n> >  2 files changed, 109 insertions(+)\n> \n> Hmph, this probably is a question better asked to Patrick, but it\n> somehow feels a bit unsatisfactory that we are duplicating instead\n> of replacing, as we cannot see \"ah, this removed thing is now added\n> in a different shape to fit in the other framework\" in the patch---\n> instead what we see in the patch is a bunch of \"a completely new\n> thing that honors the convention we are familiar in existing clar\n> based tests is added here\".\n\nWell, my expectation is that at the end of this patch series we will\nremove the old functionality. The steps should roughly be:\n\n  1. Implement equivalents for the reftable-specific test library for\n     clar.\n\n  2. Convert all reftable unit tests to use the new functions.\n\n  3. Remove the old functions.\n\nGiven that (2) is a bunch of tests it makes perfect sense to first have\na separate patch that adds without removing yet.\n\nWhat I found a bit confusing though is that the new functions aren't\nadded to \"t/unit-tests/lib-reftable.{c,h}\". This would be very much\npreferable from my point of view as the test functions aren't globally\nrelevant, but really only relevant to the reftable library.\n\nPatrick\n"},{"id":"517106","messageId":"aBSXEEjRyh2cQ4Jm@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-2-kuforiji98@gmail.com","subject":"Re: [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:36Z","receivedAt":"2025-05-02T09:57:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:52:53PM +0100, Seyi Kuforiji wrote:\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;\n> i++)`, implement equivalent helper functions in unit-test.{c,h} to use\n> clar. These functions conform with the clar testing framework and become\n> available for all reftable-related test files implemented using the clar\n> testing framework, which requires them. This will be used by subsequent\n> commits.\n> \n> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>\n> ---\n>  t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++\n>  t/unit-tests/unit-test.h | 16 +++++++\n>  2 files changed, 109 insertions(+)\n\nI think this functionality should be added to\n\"t/unit-tests/lib-reftable.{c,h}\" instead of to the generic unit testing\nlibrary as it is highly specific to reftables.\n\n> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c\n> index 5af645048a..6c2a4e6aa8 100644\n> --- a/t/unit-tests/unit-test.c\n> +++ b/t/unit-tests/unit-test.c\n> @@ -1,10 +1,103 @@\n>  #include \"unit-test.h\"\n>  #include \"hex.h\"\n>  #include \"parse-options.h\"\n> +#include \"reftable/constants.h\"\n> +#include \"reftable/writer.h\"\n>  #include \"strbuf.h\"\n>  #include \"string-list.h\"\n>  #include \"strvec.h\"\n>  \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> +\n> +static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)\n> +{\n> +\tstrbuf_add(b, data, sz);\n> +\treturn sz;\n> +}\n> +\n> +static int strbuf_writer_flush(void *arg UNUSED)\n> +{\n> +\treturn 0;\n> +}\n> +\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> +\tcl_assert(ret == 0);\n\nWe typically don't explicitly compare with zero, so this should rather\nbe `cl_assert(!ret)`.\n\n> +\treturn writer;\n> +}\n> +\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> +\t\t\t     size_t nlogs,\n> +\t\t\t     struct reftable_write_options *_opts)\n> +{\n> +\tstruct reftable_write_options opts = { 0 };\n> +\tconst struct reftable_stats *stats;\n> +\tstruct reftable_writer *writer;\n> +\tuint64_t min = 0xffffffff;\n> +\tuint64_t max = 0;\n> +\tint ret;\n> +\n> +\tif (_opts)\n> +\t\topts = *_opts;\n> +\n> +\tfor (size_t i = 0; i < nrefs; i++) {\n> +\t\tuint64_t ui = refs[i].update_index;\n> +\t\tif (ui > max)\n> +\t\t\tmax = ui;\n> +\t\tif (ui < min)\n> +\t\t\tmin = ui;\n> +\t}\n> +\tfor (size_t i = 0; i < nlogs; i++) {\n> +\t\tuint64_t ui = logs[i].update_index;\n> +\t\tif (ui > max)\n> +\t\t\tmax = ui;\n> +\t\tif (ui < min)\n> +\t\t\tmin = ui;\n> +\t}\n> +\n> +\twriter = cl_reftable_strbuf_writer(buf, &opts);\n> +\treftable_writer_set_limits(writer, min, max);\n\nThis function may return an error, as well, so let's verify it while at\nit.\n\nPatrick\n"},{"id":"517107","messageId":"aBSXFNKU5JK1DgCv@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-3-kuforiji98@gmail.com","subject":"Re: [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:40Z","receivedAt":"2025-05-02T09:57:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:52:54PM +0100, Seyi Kuforiji wrote:\n> Adapt reftable basics test file to clar by using clar assertions\n> where necessary.Break up test edge case to improve modularity and\n> clarity.\n> \n> Signed-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> \n> diff --git a/Makefile b/Makefile\n> index 13f9062a05..7b12bb078c 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> @@ -1374,7 +1375,6 @@ 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>  \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\n> diff --git a/t/meson.build b/t/meson.build\n> index bfb744e886..8a42b595d9 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> @@ -54,7 +55,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',\n> diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c\n> deleted file mode 100644\n> index 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> -}\n> diff --git a/t/unit-tests/u-reftable-basics.c b/t/unit-tests/u-reftable-basics.c\n> new file mode 100644\n> index 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\nNit: empty newline can be removed.\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\nI think you missed converting \"parse_names drops empty string\".\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\nWhy did you change the order of `reftable_buf_reset()` calls? The\nreordered logic achieves the same result, but I'd recommend to keep\nthings as-is so that reviewers aren't puzzled by this arbitrary change.\n\n> +\t}\n> +\treftable_buf_release(&a);\n> +\treftable_buf_release(&b);\n> +}\n\nI miss tests for `put_be64` and `put_be32`.\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\nLet's rename this to `__alloc_grow()` to match the name of the function.\n\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\nSame here, let's rename to `alloc_grow_or_null`.\n\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\nOther than that this looks well-done to me, thanks!\n\nPatrick\n"},{"id":"517108","messageId":"aBSXGA5GVTNlO6lL@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-8-kuforiji98@gmail.com","subject":"Re: [PATCH v2 07/10] t/unit-tests: convert reftable readwrite test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:44Z","receivedAt":"2025-05-02T09:57:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:52:59PM +0100, Seyi Kuforiji wrote:\n> diff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c\n> new file mode 100644\n> index 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\nIt feels inconsisetnt that we use `cl_assert_equal_i()` to check for `n`\nbut `cl_assert(... == 0)` here.\n\nPatrick\n"},{"id":"517109","messageId":"aBSXGz_eIljWbb2H@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-4-kuforiji98@gmail.com","subject":"Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:47Z","receivedAt":"2025-05-02T09:57:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:52:55PM +0100, Seyi Kuforiji wrote:\n> diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c\n> deleted file mode 100644\n> index 22040aeefa..0000000000\n> --- a/t/unit-tests/t-reftable-block.c\n> +++ /dev/null\n\nHm, why is this recorded as a delete and creation? Weird, inspecting the\ndiff locally properly shows it as a rename, which makes it a ton easier\nto review. It would be great if you could try to play around with the\n`--find-renames` option in the next iteration of this series and double\ncheck that these are shown as a rename.\n\n> diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c\n> new file mode 100644\n> index 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\nThis doesn't got to do anything with indices but with refs, so I'd\nrename this to `__ref_read_write()`.\n\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\nSame comment here, asserts like this can be retained as\n`cl_assert(!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> +\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\nIt's a bit unfortunate that we have to use `cl_assert()` here, but that\nisn't the fault of this series. I do have a pull request pending\nupstream that introduces integer comparisons. Once we've updated to that\nversion I'll go through our unit tests and adapt callsites accordingly.\n\n[snip]\n> +void test_reftable_block__ref_read_write(void)\n\nThis one here should be called `__index_read_write()`. I guess you\nconfused the first and and this test name with one another.\n\nPatrick\n"},{"id":"517110","messageId":"aBSXHs4nuuBhqOW1@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-5-kuforiji98@gmail.com","subject":"Re: [PATCH v2 04/10] t/unit-tests: convert reftable merged test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:50Z","receivedAt":"2025-05-02T09:57:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:52:56PM +0100, Seyi Kuforiji wrote:\n> diff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c\n> new file mode 100644\n> index 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\nThis should be `__single_record(void)`. Same for the others, the\n`__merged` prefix is somewhat duplicate as you already have it in the\ntest suite name.\n\n> +void test_reftable_merged__merged_seek_multiple_times(void)\n\nSo, same here, let's rename to `__seek_multiple_times()`.\n\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\nIndentation is wrong. Our tabs are 8 spaces, so this is indented too\ndeep now.\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\nSame here. There are also other instances in this file.\n\nPatrick\n"},{"id":"517111","messageId":"aBSXIih1QleXX8wg@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-7-kuforiji98@gmail.com","subject":"Re: [PATCH v2 06/10] t/unit-tests: convert reftable reader test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:54Z","receivedAt":"2025-05-02T09:57:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:52:58PM +0100, Seyi Kuforiji wrote:\n> diff --git a/t/unit-tests/u-reftable-reader.c b/t/unit-tests/u-reftable-reader.c\n> new file mode 100644\n> index 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\nNit: empty newline.\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\nIndentation is wrong again.\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\nHere, as well.\n\nPatrick\n"},{"id":"517112","messageId":"aBSXJcT8REHWrsrx@pks.im","threadId":"63370","inReplyTo":"20250429175302.23724-11-kuforiji98@gmail.com","subject":"Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-02T09:57:57Z","receivedAt":"2025-05-02T09:58:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:\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 change migrates the helper\n> functions back into `lib-reftable.{c,h}`.\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>  t/unit-tests/unit-test.c    | 93 -------------------------------------\n>  t/unit-tests/unit-test.h    | 16 -------\n>  6 files changed, 20 insertions(+), 129 deletions(-)\n> \n> diff --git a/Makefile b/Makefile\n> index 0b42893611..7e646e16ee 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -1379,12 +1379,12 @@ 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_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\n`UNIT_TEST_PROGS` only contains the \"unit-test\" binary now, right? Does\nthis mean that we can simplify build rules in our Makefile now?\n\n> diff --git a/t/meson.build b/t/meson.build\n> index 8fa00fc9ef..7c305a90b5 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\n> @@ -26,8 +26,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],\n\nCan't we remove this completely? `unit_test_programs` is empty now, so\nthis loop is a no-op now.\n\n> diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\n> index 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\nIt is quite weird that we declare the replacement functions in\n\"unit-test.h\" in the first commit only to remove them at a later point.\nIt would make way more sense if we introduced the functions in\n\"t/unit/lib-reftable.{c,h}\" right from the start and then only remove\nthe unused functions in the last step.\n\nPatrick\n"},{"id":"517213","messageId":"CAGedMtdRwsBwRK6ENaJfbZ1tba4jD1WMyk3HdFft3sC6H2001w@mail.gmail.com","threadId":"63370","inReplyTo":"aBSXJcT8REHWrsrx@pks.im","subject":"Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Seyi Chamber","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-05-05T07:27:16Z","receivedAt":"2025-05-05T07:27:30Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"Hi Patrick,\nOn Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:\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 change migrates the helper\n> > functions back into `lib-reftable.{c,h}`.\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> >  t/unit-tests/unit-test.c    | 93 -------------------------------------\n> >  t/unit-tests/unit-test.h    | 16 -------\n> >  6 files changed, 20 insertions(+), 129 deletions(-)\n> >\n> > diff --git a/Makefile b/Makefile\n> > index 0b42893611..7e646e16ee 100644\n> > --- a/Makefile\n> > +++ b/Makefile\n> > @@ -1379,12 +1379,12 @@ 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_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>\n> `UNIT_TEST_PROGS` only contains the \"unit-test\" binary now, right? Does\n> this mean that we can simplify build rules in our Makefile now?\n>\n> > diff --git a/t/meson.build b/t/meson.build\n> > index 8fa00fc9ef..7c305a90b5 100644\n> > --- a/t/meson.build\n> > +++ b/t/meson.build\n> > @@ -26,8 +26,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],\n>\n> Can't we remove this completely? `unit_test_programs` is empty now, so\n> this loop is a no-op now.\n>\n> > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\n> > index 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> >                                                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> >                            struct reftable_ref_record *refs,\n> >                            size_t nrecords,\n> >                            struct reftable_log_record *logs,\n>\n> It is quite weird that we declare the replacement functions in\n> \"unit-test.h\" in the first commit only to remove them at a later point.\n> It would make way more sense if we introduced the functions in\n> \"t/unit/lib-reftable.{c,h}\" right from the start and then only remove\n> the unused functions in the last step.\n>\n> Patrick\n\nIf I get it correctly, you're suggesting I have both the original\nfunctions and the clar-based variant in `t/unit/lib-reftable.{c,h}`\nright from the first commit, right?\n\nThanks\nSeyi\n"},{"id":"517216","messageId":"CAGedMtcBsT=7=tL_y99_G9xNW43Bttb3dFqy68DfFt0ZgpZ-4Q@mail.gmail.com","threadId":"63370","inReplyTo":"aBSXGz_eIljWbb2H@pks.im","subject":"Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar","fromName":"Seyi Chamber","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-05-05T07:37:27Z","receivedAt":"2025-05-05T07:37:40Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Apr 29, 2025 at 06:52:55PM +0100, Seyi Kuforiji wrote:\n> > diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c\n> > deleted file mode 100644\n> > index 22040aeefa..0000000000\n> > --- a/t/unit-tests/t-reftable-block.c\n> > +++ /dev/null\n>\n> Hm, why is this recorded as a delete and creation? Weird, inspecting the\n> diff locally properly shows it as a rename, which makes it a ton easier\n> to review. It would be great if you could try to play around with the\n> `--find-renames` option in the next iteration of this series and double\n> check that these are shown as a rename.\n>\n\nI used `--find-renames=90` for this patch series. Is there any\nrecommended number to set it to? :/\n\n> > diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c\n> > new file mode 100644\n> > index 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> This doesn't got to do anything with indices but with refs, so I'd\n> rename this to `__ref_read_write()`.\n>\n> > +{\n> > +     const int header_off = 21; /* random */\n> > +     struct reftable_record recs[30];\n> > +     const size_t N = ARRAY_SIZE(recs);\n> > +     const size_t block_size = 1024;\n> > +     struct reftable_block block = { 0 };\n> > +     struct block_writer bw = {\n> > +             .last_key = REFTABLE_BUF_INIT,\n> > +     };\n> > +     struct reftable_record rec = {\n> > +             .type = BLOCK_TYPE_REF,\n> > +     };\n> > +     size_t i = 0;\n> > +     int ret;\n> > +     struct block_reader br = { 0 };\n> > +     struct block_iter it = BLOCK_ITER_INIT;\n> > +     struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;\n> > +\n> > +     REFTABLE_CALLOC_ARRAY(block.data, block_size);\n> > +     cl_assert(block.data != NULL);\n> > +     block.len = block_size;\n> > +     block_source_from_buf(&block.source ,&buf);\n> > +     ret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,\n> > +                             header_off, hash_size(REFTABLE_HASH_SHA1));\n> > +     cl_assert(ret == 0);\n>\n> Same comment here, asserts like this can be retained as\n> `cl_assert(!ret)`.\n>\n\nohh, I didn't know this was possible.\n\n> > +     rec.u.ref.refname = (char *) \"\";\n> > +     rec.u.ref.value_type = REFTABLE_REF_DELETION;\n> > +     ret = block_writer_add(&bw, &rec);\n> > +     cl_assert_equal_i(ret, REFTABLE_API_ERROR);\n> > +\n> > +     for (i = 0; i < N; i++) {\n> > +             rec.u.ref.refname = xstrfmt(\"branch%02\"PRIuMAX, (uintmax_t)i);\n> > +             rec.u.ref.value_type = REFTABLE_REF_VAL1;\n> > +             memset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);\n> > +\n> > +             recs[i] = rec;\n> > +             ret = block_writer_add(&bw, &rec);\n> > +             rec.u.ref.refname = NULL;\n> > +             rec.u.ref.value_type = REFTABLE_REF_DELETION;\n> > +             cl_assert_equal_i(ret, 0);\n> > +     }\n> > +\n> > +     ret = block_writer_finish(&bw);\n> > +     cl_assert(ret > 0);\n>\n> It's a bit unfortunate that we have to use `cl_assert()` here, but that\n> isn't the fault of this series. I do have a pull request pending\n> upstream that introduces integer comparisons. Once we've updated to that\n> version I'll go through our unit tests and adapt callsites accordingly.\n>\n> [snip]\n> > +void test_reftable_block__ref_read_write(void)\n>\n> This one here should be called `__index_read_write()`. I guess you\n> confused the first and and this test name with one another.\n>\n> Patrick\n\nAh, okay, Thanks!\n\nBest\nSeyi\n"},{"id":"517231","messageId":"CAGedMtc49Mur-M77krEZ7NbomKn1bTW-q1MuweQa0=Vh4M-hvg@mail.gmail.com","threadId":"63370","inReplyTo":"aBSXBv0oPOXpfC2i@pks.im","subject":"Re: [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar","fromName":"Seyi Chamber","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-05-05T09:11:18Z","receivedAt":"2025-05-05T09:11:31Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Tue, Apr 29, 2025 at 06:53:01PM +0100, Seyi Kuforiji wrote:\n> > diff --git a/t/meson.build b/t/meson.build\n> > index 756cb2a2dd..8fa00fc9ef 100644\n> > --- a/t/meson.build\n> > +++ b/t/meson.build\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,\n>\n> Okay. Ideally this would be moved into the next commit where we remove\n> the infrastructure for our old-style unit tests, but we can't because\n> the glob matches nothing anymore and thus causes an error.\n>\n> Also, we have a \"check-meson\" target in \"t/Makefile\". Don't we have to\n> remove unit tests from there, too?\n>\n> Patrick\n\nYou are referring to this, yes?\n`@# awk acts up when trying to match single quotes, so we use \\047 instead.\n    @mkdir -p mesontmp && \\\n    printf \"%s\\n\" \\\n        \"integration_tests t[0-9][0-9][0-9][0-9]-*.sh\" \\\n        \"unit_test_programs unit-tests/t-*.c\" \\\n        \"clar_test_suites unit-tests/u-*.c\" | \\\n    while read -r variable pattern; do \\`\n\nI've hardly taken a look at the `t/Makefile`, given my very little\ninteraction with the file throughout the test conversions.\n\nBest\nSeyi\n"},{"id":"517239","messageId":"aBiKSeTLItw85A8z@pks.im","threadId":"63370","inReplyTo":"CAGedMtcBsT=7=tL_y99_G9xNW43Bttb3dFqy68DfFt0ZgpZ-4Q@mail.gmail.com","subject":"Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-05T09:52:09Z","receivedAt":"2025-05-05T09:52:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, May 05, 2025 at 08:37:27AM +0100, Seyi Chamber wrote:\n> On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:\n> >\n> > On Tue, Apr 29, 2025 at 06:52:55PM +0100, Seyi Kuforiji wrote:\n> > > diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c\n> > > deleted file mode 100644\n> > > index 22040aeefa..0000000000\n> > > --- a/t/unit-tests/t-reftable-block.c\n> > > +++ /dev/null\n> >\n> > Hm, why is this recorded as a delete and creation? Weird, inspecting the\n> > diff locally properly shows it as a rename, which makes it a ton easier\n> > to review. It would be great if you could try to play around with the\n> > `--find-renames` option in the next iteration of this series and double\n> > check that these are shown as a rename.\n> >\n> \n> I used `--find-renames=90` for this patch series. Is there any\n> recommended number to set it to? :/\n\nNote that \"90\" also isn't interpreted the way you think it is, at least\ngoing by git-format-patch(1):\n\n    -M[<n>], --find-renames[=<n>]\n        Detect renames. If <n> is specified, it is a threshold on the similarity index (i.e. amount\n        of addition/deletions compared to the file’s size). For example, -M90% means Git should\n        consider a delete/add pair to be a rename if more than 90% of the file hasn’t changed.\n        Without a % sign, the number is to be read as a fraction, with a decimal point before it.\n        I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly, -M05 is the same as -M5%.\n        To limit detection to exact renames, use -M100%. The default similarity index is 50%.\n\nWhat you probably wanted to say is `--find-renames=90%`, but without the\npercentage sign it is read as a fraction, where 1 means \"exact copy\" and\n0 means \"all different\".\n\nI'd recommend to just play around with this option and inspect the\noutcome until things look reviewable. :)\n\nPatrick\n"},{"id":"517240","messageId":"aBiKTU0AfgZnOtHy@pks.im","threadId":"63370","inReplyTo":"CAGedMtc49Mur-M77krEZ7NbomKn1bTW-q1MuweQa0=Vh4M-hvg@mail.gmail.com","subject":"Re: [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-05T09:52:13Z","receivedAt":"2025-05-05T09:52:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, May 05, 2025 at 10:11:18AM +0100, Seyi Chamber wrote:\n> On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:\n> >\n> > On Tue, Apr 29, 2025 at 06:53:01PM +0100, Seyi Kuforiji wrote:\n> > > diff --git a/t/meson.build b/t/meson.build\n> > > index 756cb2a2dd..8fa00fc9ef 100644\n> > > --- a/t/meson.build\n> > > +++ b/t/meson.build\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,\n> >\n> > Okay. Ideally this would be moved into the next commit where we remove\n> > the infrastructure for our old-style unit tests, but we can't because\n> > the glob matches nothing anymore and thus causes an error.\n> >\n> > Also, we have a \"check-meson\" target in \"t/Makefile\". Don't we have to\n> > remove unit tests from there, too?\n> >\n> > Patrick\n> \n> You are referring to this, yes?\n> `@# awk acts up when trying to match single quotes, so we use \\047 instead.\n>     @mkdir -p mesontmp && \\\n>     printf \"%s\\n\" \\\n>         \"integration_tests t[0-9][0-9][0-9][0-9]-*.sh\" \\\n>         \"unit_test_programs unit-tests/t-*.c\" \\\n>         \"clar_test_suites unit-tests/u-*.c\" | \\\n>     while read -r variable pattern; do \\`\n> \n> I've hardly taken a look at the `t/Makefile`, given my very little\n> interaction with the file throughout the test conversions.\n\nYes, that's the part I was referring to.\n\nPatrick\n"},{"id":"517241","messageId":"aBiKUGn7avRUcehV@pks.im","threadId":"63370","inReplyTo":"CAGedMtdRwsBwRK6ENaJfbZ1tba4jD1WMyk3HdFft3sC6H2001w@mail.gmail.com","subject":"Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-05T09:52:16Z","receivedAt":"2025-05-05T09:52:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, May 05, 2025 at 08:27:16AM +0100, Seyi Chamber wrote:\n> On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:\n> > On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:\n> > > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\n> > > index 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> > >                                                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> > >                            struct reftable_ref_record *refs,\n> > >                            size_t nrecords,\n> > >                            struct reftable_log_record *logs,\n> >\n> > It is quite weird that we declare the replacement functions in\n> > \"unit-test.h\" in the first commit only to remove them at a later point.\n> > It would make way more sense if we introduced the functions in\n> > \"t/unit/lib-reftable.{c,h}\" right from the start and then only remove\n> > the unused functions in the last step.\n> >\n> > Patrick\n> \n> If I get it correctly, you're suggesting I have both the original\n> functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`\n\nYup, exactly.\n\nPatrick\n"},{"id":"517279","messageId":"xmqq7c2uoj40.fsf@gitster.g","threadId":"63370","inReplyTo":"aBiKSeTLItw85A8z@pks.im","subject":"Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-05T21:14:39Z","receivedAt":"2025-05-05T21:14:42Z","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>     -M[<n>], --find-renames[=<n>]\n>         Detect renames. If <n> is specified, it is a threshold on the similarity index (i.e. amount\n>         of addition/deletions compared to the file’s size). For example, -M90% means Git should\n>         consider a delete/add pair to be a rename if more than 90% of the file hasn’t changed.\n>         Without a % sign, the number is to be read as a fraction, with a decimal point before it.\n>         I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly, -M05 is the same as -M5%.\n>         To limit detection to exact renames, use -M100%. The default similarity index is 50%.\n>\n> What you probably wanted to say is `--find-renames=90%`, but without the\n> percentage sign it is read as a fraction, where 1 means \"exact copy\" and\n> 0 means \"all different\".\n\nI am confused.  -M<number> without trailing %-sign is taken as\nfraction against 1 followed by the same number of '0' has the\n<number> has digits.  -M5 is 5 over 10, -M50 is 50 over 100.  -M90\nis 90 over 100, so -M90 and -M90% should mean the same thing.\n\nBut you are right.  When you want to claim your pre- and post- image\nfiles still correspond with each other in a meaningful way, even\nafter making extensive change, you would want to _lower_, not raise,\nyour similarity threshold.  If the default is -M50, then -M90 would\nbe a useful option to reject what Git (mistakenly) thinks are renames\nand tell it to instead consider they are removals and creations.\n\nTHanks.\n"},{"id":"517288","messageId":"aBmZtFlcn8CunVg_@pks.im","threadId":"63370","inReplyTo":"xmqq7c2uoj40.fsf@gitster.g","subject":"Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-06T05:10:12Z","receivedAt":"2025-05-06T05:10:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, May 05, 2025 at 02:14:39PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >     -M[<n>], --find-renames[=<n>]\n> >         Detect renames. If <n> is specified, it is a threshold on the similarity index (i.e. amount\n> >         of addition/deletions compared to the file’s size). For example, -M90% means Git should\n> >         consider a delete/add pair to be a rename if more than 90% of the file hasn’t changed.\n> >         Without a % sign, the number is to be read as a fraction, with a decimal point before it.\n> >         I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly, -M05 is the same as -M5%.\n> >         To limit detection to exact renames, use -M100%. The default similarity index is 50%.\n> >\n> > What you probably wanted to say is `--find-renames=90%`, but without the\n> > percentage sign it is read as a fraction, where 1 means \"exact copy\" and\n> > 0 means \"all different\".\n> \n> I am confused.  -M<number> without trailing %-sign is taken as\n> fraction against 1 followed by the same number of '0' has the\n> <number> has digits.  -M5 is 5 over 10, -M50 is 50 over 100.  -M90\n> is 90 over 100, so -M90 and -M90% should mean the same thing.\n\nHm, I guess _I_ was confused then. It doesn't feel natural to me that\n-M90 would mean 90% whereas -M5 means 50%, and the documentation isn't\nquite clear about this, either.\n\n> But you are right.  When you want to claim your pre- and post- image\n> files still correspond with each other in a meaningful way, even\n> after making extensive change, you would want to _lower_, not raise,\n> your similarity threshold.  If the default is -M50, then -M90 would\n> be a useful option to reject what Git (mistakenly) thinks are renames\n> and tell it to instead consider they are removals and creations.\n\nYeah, at least that point stands :)\n\nPatrick\n"},{"id":"518911","messageId":"CAGedMteAkHj0xc2iq7T+CWa=uJWWg0me8hD6x=Nv8PHPtO1P_w@mail.gmail.com","threadId":"63370","inReplyTo":"aBiKUGn7avRUcehV@pks.im","subject":"Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Seyi Chamber","fromEmail":"kuforiji98@gmail.com","sentAt":"2025-05-26T09:04:43Z","receivedAt":"2025-05-26T09:04:58Z","isPatch":true,"sender":{"key":"kuforiji98@gmail.com","avatar":"https://avatars.githubusercontent.com/u/94905626?v=4"},"body":"On Mon, 5 May 2025 at 10:52, Patrick Steinhardt <ps@pks.im> wrote:\n>\n> On Mon, May 05, 2025 at 08:27:16AM +0100, Seyi Chamber wrote:\n> > On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:\n> > > On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:\n> > > > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\n> > > > index 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> > > >                                                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> > > >                            struct reftable_ref_record *refs,\n> > > >                            size_t nrecords,\n> > > >                            struct reftable_log_record *logs,\n> > >\n> > > It is quite weird that we declare the replacement functions in\n> > > \"unit-test.h\" in the first commit only to remove them at a later point.\n> > > It would make way more sense if we introduced the functions in\n> > > \"t/unit/lib-reftable.{c,h}\" right from the start and then only remove\n> > > the unused functions in the last step.\n> > >\n> > > Patrick\n> >\n> > If I get it correctly, you're suggesting I have both the original\n> > functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`\n>\n> Yup, exactly.\n>\n> Patrick\n\nHi Patrick,\n\nThanks for the suggestion to move both the original t-helpers and the\nClar-based cl_ versions into `t/unit/lib-reftable.{c,h}`.\n\nI’ve tried doing that but ran into some build issues. The Clar-based\nfunctions use clar_assert and clar_assert_equal, which aren’t\navailable to non-Clar tests. Since both sets of helpers would live in\nthe same object file, this causes linker errors for binaries that\ndon’t link against Clar.\n\nI also hit Makefile warnings like “target 'lib-reftable.o' given more\nthan once,” due to the object being included in both Clar and non-Clar\ntest builds. And with all declarations in one header, non-Clar tests\nsee prototypes for Clar-only functions they can't link.\n\nAlso facing errors like these:\n`LINK t/unit-tests/bin/t-reftable-basics\n/usr/bin/ld: t/unit-tests/lib-reftable.o: in function\n`cl_reftable_strbuf_writer':\n/home/seyik/git/t/unit-tests/lib-reftable.c:113:(.text+0x520):\nundefined reference to `clar__assert'`\n\nWhat would you recommend I do to fix this?\n\nThanks,\nSeyi\n"},{"id":"518925","messageId":"aDRk8XeQLWn_jQl3@pks.im","threadId":"63370","inReplyTo":"CAGedMteAkHj0xc2iq7T+CWa=uJWWg0me8hD6x=Nv8PHPtO1P_w@mail.gmail.com","subject":"Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-05-26T12:56:17Z","receivedAt":"2025-05-26T12:56:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, May 26, 2025 at 10:04:43AM +0100, Seyi Chamber wrote:\n> On Mon, 5 May 2025 at 10:52, Patrick Steinhardt <ps@pks.im> wrote:\n> >\n> > On Mon, May 05, 2025 at 08:27:16AM +0100, Seyi Chamber wrote:\n> > > On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:\n> > > > On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:\n> > > > > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h\n> > > > > index 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> > > > >                                                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> > > > >                            struct reftable_ref_record *refs,\n> > > > >                            size_t nrecords,\n> > > > >                            struct reftable_log_record *logs,\n> > > >\n> > > > It is quite weird that we declare the replacement functions in\n> > > > \"unit-test.h\" in the first commit only to remove them at a later point.\n> > > > It would make way more sense if we introduced the functions in\n> > > > \"t/unit/lib-reftable.{c,h}\" right from the start and then only remove\n> > > > the unused functions in the last step.\n> > > >\n> > > > Patrick\n> > >\n> > > If I get it correctly, you're suggesting I have both the original\n> > > functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`\n> >\n> > Yup, exactly.\n> >\n> > Patrick\n> \n> Hi Patrick,\n> \n> Thanks for the suggestion to move both the original t-helpers and the\n> Clar-based cl_ versions into `t/unit/lib-reftable.{c,h}`.\n> \n> I’ve tried doing that but ran into some build issues. The Clar-based\n> functions use clar_assert and clar_assert_equal, which aren’t\n> available to non-Clar tests. Since both sets of helpers would live in\n> the same object file, this causes linker errors for binaries that\n> don’t link against Clar.\n> \n> I also hit Makefile warnings like “target 'lib-reftable.o' given more\n> than once,” due to the object being included in both Clar and non-Clar\n> test builds. And with all declarations in one header, non-Clar tests\n> see prototypes for Clar-only functions they can't link.\n> \n> Also facing errors like these:\n> `LINK t/unit-tests/bin/t-reftable-basics\n> /usr/bin/ld: t/unit-tests/lib-reftable.o: in function\n> `cl_reftable_strbuf_writer':\n> /home/seyik/git/t/unit-tests/lib-reftable.c:113:(.text+0x520):\n> undefined reference to `clar__assert'`\n> \n> What would you recommend I do to fix this?\n\nHm. You could also have a separate \"lib-reftable-clar.{c,h}\" code unit\nthat contains the new definitions that you introduce early in your\nseries. At the end of the series you could then delete the old\n\"lib-reftable.{c,h}\" and rename \"lib-reftable-clar.{c,h}\" accordingly to\nhave that name. It's requires a bit more shuffling, but given that the\nfinal commit would then be a plain rename it shouldn't be too bad, I\nguess.\n\nPatrick\n"}]}