git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar
@ 2025-04-29 17:52 Seyi Kuforiji
  2025-04-29 17:52 ` [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h} Seyi Kuforiji
                   ` (9 more replies)
  0 siblings, 10 replies; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Hello,

This small patch series marks the final batch of our existing unit test
files transitioned to the Clar testing framework. It covers all the
reftable-related test files, and is part of our ongoing effort to
standardize our testing framework to enhance its maintainability.

Changes in v2:
 - some code refactoring based on review, which includes 
 temporary relocation of reftable helper functions to 
 t/unit-tests/unit-test.{c,h} files in the initial commit

Thanks
Seyi

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>

Seyi Kuforiji (10):
  t/unit-tests: implement reftable test helper functions in
    unit-test.{c,h}
  t/unit-tests: convert reftable basics test to use clar test framework
  t/unit-tests: convert reftable block test to use clar
  t/unit-tests: convert reftable merged test to use clar
  t/unit-tests: convert reftable pq test to use clar
  t/unit-tests: convert reftable reader test to use clar
  t/unit-tests: convert reftable readwrite test to use clar
  t/unit-tests: convert reftable record test to use clar
  t/unit-tests: convert reftable stack test to use clar
  t/unit-tests: adapt lib-reftable{c,h} helper functions to clar

 Makefile                            |   20 +-
 t/meson.build                       |   21 +-
 t/unit-tests/lib-reftable.c         |   26 +-
 t/unit-tests/lib-reftable.h         |    6 +-
 t/unit-tests/t-reftable-basics.c    |  219 ----
 t/unit-tests/t-reftable-block.c     |  383 -------
 t/unit-tests/t-reftable-merged.c    |  546 ----------
 t/unit-tests/t-reftable-pq.c        |  161 ---
 t/unit-tests/t-reftable-reader.c    |   96 --
 t/unit-tests/t-reftable-readwrite.c |  985 ------------------
 t/unit-tests/t-reftable-record.c    |  585 -----------
 t/unit-tests/t-reftable-stack.c     | 1451 ---------------------------
 t/unit-tests/u-reftable-basics.c    |  195 ++++
 t/unit-tests/u-reftable-block.c     |  373 +++++++
 t/unit-tests/u-reftable-merged.c    |  515 ++++++++++
 t/unit-tests/u-reftable-pq.c        |  152 +++
 t/unit-tests/u-reftable-reader.c    |   78 ++
 t/unit-tests/u-reftable-readwrite.c |  870 ++++++++++++++++
 t/unit-tests/u-reftable-record.c    |  565 +++++++++++
 t/unit-tests/u-reftable-stack.c     | 1247 +++++++++++++++++++++++
 20 files changed, 4031 insertions(+), 4463 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-basics.c
 delete mode 100644 t/unit-tests/t-reftable-block.c
 delete mode 100644 t/unit-tests/t-reftable-merged.c
 delete mode 100644 t/unit-tests/t-reftable-pq.c
 delete mode 100644 t/unit-tests/t-reftable-reader.c
 delete mode 100644 t/unit-tests/t-reftable-readwrite.c
 delete mode 100644 t/unit-tests/t-reftable-record.c
 delete mode 100644 t/unit-tests/t-reftable-stack.c
 create mode 100644 t/unit-tests/u-reftable-basics.c
 create mode 100644 t/unit-tests/u-reftable-block.c
 create mode 100644 t/unit-tests/u-reftable-merged.c
 create mode 100644 t/unit-tests/u-reftable-pq.c
 create mode 100644 t/unit-tests/u-reftable-reader.c
 create mode 100644 t/unit-tests/u-reftable-readwrite.c
 create mode 100644 t/unit-tests/u-reftable-record.c
 create mode 100644 t/unit-tests/u-reftable-stack.c

Range-diff against v1:
 -:  ---------- >  1:  d85c5694ca t/unit-tests: implement reftable test helper functions in unit-test.{c,h}
 2:  4c5fc06603 !  2:  d24faa0176 t/unit-tests: convert reftable basics test to use clar test framework
    @@ Makefile: CLAR_TEST_SUITES += u-oid-array
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-basics
      UNIT_TEST_PROGRAMS += t-reftable-block
 3:  c9bdff8290 !  3:  d9eb47645b t/unit-tests: convert reftable block test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-oidmap
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-block
      UNIT_TEST_PROGRAMS += t-reftable-merged
 4:  60e2ff8bca !  4:  6ffc7cdfc6 t/unit-tests: convert reftable merged test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-oidtree
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-merged
      UNIT_TEST_PROGRAMS += t-reftable-pq
 5:  b06520b033 !  5:  19b08968a0 t/unit-tests: convert reftable pq test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-prio-queue
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-pq
      UNIT_TEST_PROGRAMS += t-reftable-reader
 6:  8b8ad57ff5 !  6:  b34d6714ed t/unit-tests: convert reftable reader test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-reftable-basics
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-reader
      UNIT_TEST_PROGRAMS += t-reftable-readwrite
 7:  da73e8c85b !  7:  05c909ea94 t/unit-tests: convert reftable readwrite test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-reftable-block
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-readwrite
      UNIT_TEST_PROGRAMS += t-reftable-record
 8:  ac6fd74321 !  8:  1515cf15b7 t/unit-tests: convert reftable record test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-reftable-merged
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-record
      UNIT_TEST_PROGRAMS += t-reftable-stack
 9:  5bc6a599c6 !  9:  dcbb2b6e56 t/unit-tests: convert reftable stack test to use clar
    @@ Makefile: CLAR_TEST_SUITES += u-reftable-pq
      CLAR_TEST_SUITES += u-reftable-tree
      CLAR_TEST_SUITES += u-strbuf
      CLAR_TEST_SUITES += u-strcmp-offset
    -@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
    - CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    +@@ Makefile: CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
      CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
    + CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
      
     -UNIT_TEST_PROGRAMS += t-reftable-stack
      UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
      UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
    - 
    + UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
     
      ## t/meson.build ##
     @@ t/meson.build: clar_test_suites = [
 1:  304d17fef8 ! 10:  79aa0145aa t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
    @@ Commit message
         Adapt functions in lib-reftable.{c,h} to use clar. These functions
         conform with the clar testing framework and become available for all
         reftable-related test files implemented using the clar testing
    -    framework, which requires them. This will be used by subsequent commits.
    +    framework, which requires them. This change migrates the helper
    +    functions back into `lib-reftable.{c,h}`.
     
         Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
     
    @@ Makefile: CLAR_TEST_SUITES += u-urlmatch-normalization
     +CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
     +CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
      
    - UNIT_TEST_PROGRAMS += t-reftable-basics
    - UNIT_TEST_PROGRAMS += t-reftable-block
    -@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-record
    - UNIT_TEST_PROGRAMS += t-reftable-stack
      UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
      UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
     -UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
    @@ t/unit-tests/lib-reftable.h
      			     struct reftable_ref_record *refs,
      			     size_t nrecords,
      			     struct reftable_log_record *logs,
    +
    + ## t/unit-tests/unit-test.c ##
    +@@
    + #include "unit-test.h"
    + #include "hex.h"
    + #include "parse-options.h"
    +-#include "reftable/constants.h"
    +-#include "reftable/writer.h"
    + #include "strbuf.h"
    + #include "string-list.h"
    + #include "strvec.h"
    + 
    +-void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)
    +-{
    +-	memset(p, (uint8_t)i, hash_size(id));
    +-}
    +-
    +-static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)
    +-{
    +-	strbuf_add(b, data, sz);
    +-	return sz;
    +-}
    +-
    +-static int strbuf_writer_flush(void *arg UNUSED)
    +-{
    +-	return 0;
    +-}
    +-
    +-struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
    +-						 struct reftable_write_options *opts)
    +-{
    +-	struct reftable_writer *writer;
    +-	int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,
    +-				      buf, opts);
    +-	cl_assert(ret == 0);
    +-	return writer;
    +-}
    +-
    +-void cl_reftable_write_to_buf(struct reftable_buf *buf,
    +-			     struct reftable_ref_record *refs,
    +-			     size_t nrefs,
    +-			     struct reftable_log_record *logs,
    +-			     size_t nlogs,
    +-			     struct reftable_write_options *_opts)
    +-{
    +-	struct reftable_write_options opts = { 0 };
    +-	const struct reftable_stats *stats;
    +-	struct reftable_writer *writer;
    +-	uint64_t min = 0xffffffff;
    +-	uint64_t max = 0;
    +-	int ret;
    +-
    +-	if (_opts)
    +-		opts = *_opts;
    +-
    +-	for (size_t i = 0; i < nrefs; i++) {
    +-		uint64_t ui = refs[i].update_index;
    +-		if (ui > max)
    +-			max = ui;
    +-		if (ui < min)
    +-			min = ui;
    +-	}
    +-	for (size_t i = 0; i < nlogs; i++) {
    +-		uint64_t ui = logs[i].update_index;
    +-		if (ui > max)
    +-			max = ui;
    +-		if (ui < min)
    +-			min = ui;
    +-	}
    +-
    +-	writer = cl_reftable_strbuf_writer(buf, &opts);
    +-	reftable_writer_set_limits(writer, min, max);
    +-
    +-	if (nrefs) {
    +-		ret = reftable_writer_add_refs(writer, refs, nrefs);
    +-		cl_assert_equal_i(ret, 0);
    +-	}
    +-
    +-	if (nlogs) {
    +-		ret = reftable_writer_add_logs(writer, logs, nlogs);
    +-		cl_assert_equal_i(ret, 0);
    +-	}
    +-
    +-	ret = reftable_writer_close(writer);
    +-	cl_assert_equal_i(ret, 0);
    +-
    +-	stats = reftable_writer_stats(writer);
    +-	for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++) {
    +-		size_t off = i * (opts.block_size ? opts.block_size
    +-						  : DEFAULT_BLOCK_SIZE);
    +-		if (!off)
    +-			off = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);
    +-		cl_assert(buf->buf[off] == 'r');
    +-	}
    +-
    +-	if (nrefs)
    +-		cl_assert(stats->ref_stats.blocks > 0);
    +-	if (nlogs)
    +-		cl_assert(stats->log_stats.blocks > 0);
    +-
    +-	reftable_writer_free(writer);
    +-}
    +-
    + static const char * const unit_test_usage[] = {
    + 	N_("unit-test [<options>]"),
    + 	NULL,
    +
    + ## t/unit-tests/unit-test.h ##
    +@@
    + #include "git-compat-util.h"
    + #include "clar/clar.h"
    + #include "clar-decls.h"
    +-#include "git-compat-util.h"
    +-#include "reftable/reftable-writer.h"
    + #include "strbuf.h"
    + 
    +-struct reftable_buf;
    +-
    +-void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
    +-
    +-struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
    +-						 struct reftable_write_options *opts);
    +-
    +-void cl_reftable_write_to_buf(struct reftable_buf *buf,
    +-			     struct reftable_ref_record *refs,
    +-			     size_t nrecords,
    +-			     struct reftable_log_record *logs,
    +-			     size_t nlogs,
    +-			     struct reftable_write_options *opts);
    +-
    + #define cl_failf(fmt, ...) do { \
    + 	char desc[4096]; \
    + 	snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
-- 
2.43.0


^ permalink raw reply	[flat|nested] 31+ messages in thread

* [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-04-29 23:04   ` Junio C Hamano
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:52 ` [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework Seyi Kuforiji
                   ` (8 subsequent siblings)
  9 siblings, 2 replies; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are
required for the reftable-related test files to run efficeintly. In the
current implementation these functions are designed to conform with our
homegrown unit-testing structure. So in other to convert the reftable
test files, there is need for a clar specific implementation of these
helper functions.

type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks;
i++)`, implement equivalent helper functions in unit-test.{c,h} to use
clar. These functions conform with the clar testing framework and become
available for all reftable-related test files implemented using the clar
testing framework, which requires them. This will be used by subsequent
commits.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++
 t/unit-tests/unit-test.h | 16 +++++++
 2 files changed, 109 insertions(+)

diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 5af645048a..6c2a4e6aa8 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -1,10 +1,103 @@
 #include "unit-test.h"
 #include "hex.h"
 #include "parse-options.h"
+#include "reftable/constants.h"
+#include "reftable/writer.h"
 #include "strbuf.h"
 #include "string-list.h"
 #include "strvec.h"
 
+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)
+{
+	memset(p, (uint8_t)i, hash_size(id));
+}
+
+static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)
+{
+	strbuf_add(b, data, sz);
+	return sz;
+}
+
+static int strbuf_writer_flush(void *arg UNUSED)
+{
+	return 0;
+}
+
+struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
+						 struct reftable_write_options *opts)
+{
+	struct reftable_writer *writer;
+	int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,
+				      buf, opts);
+	cl_assert(ret == 0);
+	return writer;
+}
+
+void cl_reftable_write_to_buf(struct reftable_buf *buf,
+			     struct reftable_ref_record *refs,
+			     size_t nrefs,
+			     struct reftable_log_record *logs,
+			     size_t nlogs,
+			     struct reftable_write_options *_opts)
+{
+	struct reftable_write_options opts = { 0 };
+	const struct reftable_stats *stats;
+	struct reftable_writer *writer;
+	uint64_t min = 0xffffffff;
+	uint64_t max = 0;
+	int ret;
+
+	if (_opts)
+		opts = *_opts;
+
+	for (size_t i = 0; i < nrefs; i++) {
+		uint64_t ui = refs[i].update_index;
+		if (ui > max)
+			max = ui;
+		if (ui < min)
+			min = ui;
+	}
+	for (size_t i = 0; i < nlogs; i++) {
+		uint64_t ui = logs[i].update_index;
+		if (ui > max)
+			max = ui;
+		if (ui < min)
+			min = ui;
+	}
+
+	writer = cl_reftable_strbuf_writer(buf, &opts);
+	reftable_writer_set_limits(writer, min, max);
+
+	if (nrefs) {
+		ret = reftable_writer_add_refs(writer, refs, nrefs);
+		cl_assert_equal_i(ret, 0);
+	}
+
+	if (nlogs) {
+		ret = reftable_writer_add_logs(writer, logs, nlogs);
+		cl_assert_equal_i(ret, 0);
+	}
+
+	ret = reftable_writer_close(writer);
+	cl_assert_equal_i(ret, 0);
+
+	stats = reftable_writer_stats(writer);
+	for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++) {
+		size_t off = i * (opts.block_size ? opts.block_size
+						  : DEFAULT_BLOCK_SIZE);
+		if (!off)
+			off = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);
+		cl_assert(buf->buf[off] == 'r');
+	}
+
+	if (nrefs)
+		cl_assert(stats->ref_stats.blocks > 0);
+	if (nlogs)
+		cl_assert(stats->log_stats.blocks > 0);
+
+	reftable_writer_free(writer);
+}
+
 static const char * const unit_test_usage[] = {
 	N_("unit-test [<options>]"),
 	NULL,
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
index 85e5d6a948..fe0aebd876 100644
--- a/t/unit-tests/unit-test.h
+++ b/t/unit-tests/unit-test.h
@@ -1,8 +1,24 @@
 #include "git-compat-util.h"
 #include "clar/clar.h"
 #include "clar-decls.h"
+#include "git-compat-util.h"
+#include "reftable/reftable-writer.h"
 #include "strbuf.h"
 
+struct reftable_buf;
+
+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
+
+struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
+						 struct reftable_write_options *opts);
+
+void cl_reftable_write_to_buf(struct reftable_buf *buf,
+			     struct reftable_ref_record *refs,
+			     size_t nrecords,
+			     struct reftable_log_record *logs,
+			     size_t nlogs,
+			     struct reftable_write_options *opts);
+
 #define cl_failf(fmt, ...) do { \
 	char desc[4096]; \
 	snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
  2025-04-29 17:52 ` [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h} Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:52 ` [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar Seyi Kuforiji
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable basics test file to clar by using clar assertions
where necessary.Break up test edge case to improve modularity and
clarity.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                         |   2 +-
 t/meson.build                    |   2 +-
 t/unit-tests/t-reftable-basics.c | 219 -------------------------------
 t/unit-tests/u-reftable-basics.c | 195 +++++++++++++++++++++++++++
 4 files changed, 197 insertions(+), 221 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-basics.c
 create mode 100644 t/unit-tests/u-reftable-basics.c

diff --git a/Makefile b/Makefile
index 13f9062a05..7b12bb078c 100644
--- a/Makefile
+++ b/Makefile
@@ -1362,6 +1362,7 @@ CLAR_TEST_SUITES += u-oid-array
 CLAR_TEST_SUITES += u-oidmap
 CLAR_TEST_SUITES += u-oidtree
 CLAR_TEST_SUITES += u-prio-queue
+CLAR_TEST_SUITES += u-reftable-basics
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1374,7 +1375,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-basics
 UNIT_TEST_PROGRAMS += t-reftable-block
 UNIT_TEST_PROGRAMS += t-reftable-merged
 UNIT_TEST_PROGRAMS += t-reftable-pq
diff --git a/t/meson.build b/t/meson.build
index bfb744e886..8a42b595d9 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -8,6 +8,7 @@ clar_test_suites = [
   'unit-tests/u-oidmap.c',
   'unit-tests/u-oidtree.c',
   'unit-tests/u-prio-queue.c',
+  'unit-tests/u-reftable-basics.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -54,7 +55,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-basics.c',
   'unit-tests/t-reftable-block.c',
   'unit-tests/t-reftable-merged.c',
   'unit-tests/t-reftable-pq.c',
diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c
deleted file mode 100644
index c9e751e49e..0000000000
--- a/t/unit-tests/t-reftable-basics.c
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "test-lib.h"
-#include "reftable/basics.h"
-
-struct integer_needle_lesseq_args {
-	int needle;
-	int *haystack;
-};
-
-static int integer_needle_lesseq(size_t i, void *_args)
-{
-	struct integer_needle_lesseq_args *args = _args;
-	return args->needle <= args->haystack[i];
-}
-
-static void *realloc_stub(void *p UNUSED, size_t size UNUSED)
-{
-	return NULL;
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	if_test ("binary search with binsearch works") {
-		int haystack[] = { 2, 4, 6, 8, 10 };
-		struct {
-			int needle;
-			size_t expected_idx;
-		} testcases[] = {
-			{-9000, 0},
-			{-1, 0},
-			{0, 0},
-			{2, 0},
-			{3, 1},
-			{4, 1},
-			{7, 3},
-			{9, 4},
-			{10, 4},
-			{11, 5},
-			{9000, 5},
-		};
-
-		for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
-			struct integer_needle_lesseq_args args = {
-				.haystack = haystack,
-				.needle = testcases[i].needle,
-			};
-			size_t idx;
-
-			idx = binsearch(ARRAY_SIZE(haystack),
-					&integer_needle_lesseq, &args);
-			check_int(idx, ==, testcases[i].expected_idx);
-		}
-	}
-
-	if_test ("names_length returns size of a NULL-terminated string array") {
-		const char *a[] = { "a", "b", NULL };
-		check_int(names_length(a), ==, 2);
-	}
-
-	if_test ("names_equal compares NULL-terminated string arrays") {
-		const char *a[] = { "a", "b", "c", NULL };
-		const char *b[] = { "a", "b", "d", NULL };
-		const char *c[] = { "a", "b", NULL };
-
-		check(names_equal(a, a));
-		check(!names_equal(a, b));
-		check(!names_equal(a, c));
-	}
-
-	if_test ("parse_names works for basic input") {
-		char in1[] = "line\n";
-		char in2[] = "a\nb\nc";
-		char **out = parse_names(in1, strlen(in1));
-		check(out != NULL);
-		check_str(out[0], "line");
-		check(!out[1]);
-		free_names(out);
-
-		out = parse_names(in2, strlen(in2));
-		check(out != NULL);
-		check_str(out[0], "a");
-		check_str(out[1], "b");
-		check_str(out[2], "c");
-		check(!out[3]);
-		free_names(out);
-	}
-
-	if_test ("parse_names drops empty string") {
-		char in[] = "a\n\nb\n";
-		char **out = parse_names(in, strlen(in));
-		check(out != NULL);
-		check_str(out[0], "a");
-		/* simply '\n' should be dropped as empty string */
-		check_str(out[1], "b");
-		check(!out[2]);
-		free_names(out);
-	}
-
-	if_test ("common_prefix_size works") {
-		struct reftable_buf a = REFTABLE_BUF_INIT;
-		struct reftable_buf b = REFTABLE_BUF_INIT;
-		struct {
-			const char *a, *b;
-			int want;
-		} cases[] = {
-			{"abcdef", "abc", 3},
-			{ "abc", "ab", 2 },
-			{ "", "abc", 0 },
-			{ "abc", "abd", 2 },
-			{ "abc", "pqr", 0 },
-		};
-
-		for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
-			check(!reftable_buf_addstr(&a, cases[i].a));
-			check(!reftable_buf_addstr(&b, cases[i].b));
-			check_uint(common_prefix_size(&a, &b), ==, cases[i].want);
-			reftable_buf_reset(&a);
-			reftable_buf_reset(&b);
-		}
-		reftable_buf_release(&a);
-		reftable_buf_release(&b);
-	}
-
-	if_test ("reftable_put_be64 and reftable_get_be64 work") {
-		uint64_t in = 0x1122334455667788;
-		uint8_t dest[8];
-		uint64_t out;
-		reftable_put_be64(dest, in);
-		out = reftable_get_be64(dest);
-		check_int(in, ==, out);
-	}
-
-	if_test ("reftable_put_be32 and reftable_get_be32 work") {
-		uint32_t in = 0x11223344;
-		uint8_t dest[4];
-		uint32_t out;
-		reftable_put_be32(dest, in);
-		out = reftable_get_be32(dest);
-		check_int(in, ==, out);
-	}
-
-	if_test ("reftable_put_be24 and reftable_get_be24 work") {
-		uint32_t in = 0x112233;
-		uint8_t dest[3];
-		uint32_t out;
-		reftable_put_be24(dest, in);
-		out = reftable_get_be24(dest);
-		check_int(in, ==, out);
-	}
-
-	if_test ("put_be16 and get_be16 work") {
-		uint32_t in = 0xfef1;
-		uint8_t dest[3];
-		uint32_t out;
-		reftable_put_be16(dest, in);
-		out = reftable_get_be16(dest);
-		check_int(in, ==, out);
-	}
-
-	if_test ("REFTABLE_ALLOC_GROW works") {
-		int *arr = NULL, *old_arr;
-		size_t alloc = 0, old_alloc;
-
-		check(!REFTABLE_ALLOC_GROW(arr, 1, alloc));
-		check(arr != NULL);
-		check_uint(alloc, >=, 1);
-		arr[0] = 42;
-
-		old_alloc = alloc;
-		old_arr = arr;
-		reftable_set_alloc(NULL, realloc_stub, NULL);
-		check(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
-		check(arr == old_arr);
-		check_uint(alloc, ==, old_alloc);
-
-		old_alloc = alloc;
-		reftable_set_alloc(NULL, NULL, NULL);
-		check(!REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
-		check(arr != NULL);
-		check_uint(alloc, >, old_alloc);
-		arr[alloc - 1] = 42;
-
-		reftable_free(arr);
-	}
-
-	if_test ("REFTABLE_ALLOC_GROW_OR_NULL works") {
-		int *arr = NULL;
-		size_t alloc = 0, old_alloc;
-
-		REFTABLE_ALLOC_GROW_OR_NULL(arr, 1, alloc);
-		check(arr != NULL);
-		check_uint(alloc, >=, 1);
-		arr[0] = 42;
-
-		old_alloc = alloc;
-		REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
-		check(arr != NULL);
-		check_uint(alloc, >, old_alloc);
-		arr[alloc - 1] = 42;
-
-		old_alloc = alloc;
-		reftable_set_alloc(NULL, realloc_stub, NULL);
-		REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
-		check(arr == NULL);
-		check_uint(alloc, ==, 0);
-		reftable_set_alloc(NULL, NULL, NULL);
-
-		reftable_free(arr);
-	}
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-basics.c b/t/unit-tests/u-reftable-basics.c
new file mode 100644
index 0000000000..63dd568faf
--- /dev/null
+++ b/t/unit-tests/u-reftable-basics.c
@@ -0,0 +1,195 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "unit-test.h"
+#include "reftable/basics.h"
+
+struct integer_needle_lesseq_args {
+	int needle;
+	int *haystack;
+};
+
+static int integer_needle_lesseq(size_t i, void *_args)
+{
+	struct integer_needle_lesseq_args *args = _args;
+	return args->needle <= args->haystack[i];
+}
+
+static void *realloc_stub(void *p UNUSED, size_t size UNUSED)
+{
+	return NULL;
+}
+
+void test_reftable_basics__binsearch(void)
+{
+	int haystack[] = { 2, 4, 6, 8, 10 };
+	struct {
+		int needle;
+		size_t expected_idx;
+	} testcases[] = {
+		{-9000, 0},
+		{-1, 0},
+		{0, 0},
+		{2, 0},
+		{3, 1},
+		{4, 1},
+		{7, 3},
+		{9, 4},
+		{10, 4},
+		{11, 5},
+		{9000, 5},
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
+		struct integer_needle_lesseq_args args = {
+			.haystack = haystack,
+			.needle = testcases[i].needle,
+		};
+		size_t idx;
+
+		idx = binsearch(ARRAY_SIZE(haystack),
+				&integer_needle_lesseq, &args);
+		cl_assert_equal_i(idx, testcases[i].expected_idx);
+	}
+
+}
+
+void test_reftable_basics__names_length(void)
+{
+	const char *a[] = { "a", "b", NULL };
+	cl_assert_equal_i(names_length(a), 2);
+}
+
+void test_reftable_basics__names_equal(void)
+{
+	const char *a[] = { "a", "b", "c", NULL };
+	const char *b[] = { "a", "b", "d", NULL };
+	const char *c[] = { "a", "b", NULL };
+
+	cl_assert(names_equal(a, a));
+	cl_assert(!names_equal(a, b));
+	cl_assert(!names_equal(a, c));
+}
+
+void test_reftable_basics__parse_names(void)
+{
+	char in1[] = "line\n";
+	char in2[] = "a\nb\nc";
+	char **out = parse_names(in1, strlen(in1));
+	cl_assert(out != NULL);
+	cl_assert_equal_s(out[0], "line");
+	cl_assert(!out[1]);
+	free_names(out);
+
+	out = parse_names(in2, strlen(in2));
+	cl_assert(out != NULL);
+	cl_assert_equal_s(out[0], "a");
+	cl_assert_equal_s(out[1], "b");
+	cl_assert_equal_s(out[2], "c");
+	cl_assert(!out[3]);
+	free_names(out);
+}
+
+void test_reftable_basics__common_prefix_size(void)
+{
+	struct reftable_buf a = REFTABLE_BUF_INIT;
+	struct reftable_buf b = REFTABLE_BUF_INIT;
+	struct {
+		const char *a, *b;
+		int want;
+	} cases[] = {
+		{"abcdef", "abc", 3},
+		{ "abc", "ab", 2 },
+		{ "", "abc", 0 },
+		{ "abc", "abd", 2 },
+		{ "abc", "pqr", 0 },
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
+		reftable_buf_reset(&a);
+		reftable_buf_reset(&b);
+		cl_assert_equal_i(reftable_buf_addstr(&a, cases[i].a), 0);
+		cl_assert_equal_i(reftable_buf_addstr(&b, cases[i].b), 0);
+		cl_assert_equal_i(common_prefix_size(&a, &b), cases[i].want);
+	}
+	reftable_buf_release(&a);
+	reftable_buf_release(&b);
+}
+
+void test_reftable_basics__put_get_be24(void)
+{
+	uint32_t in = 0x112233;
+	uint8_t dest[3];
+	uint32_t out;
+	reftable_put_be24(dest, in);
+	out = reftable_get_be24(dest);
+	cl_assert_equal_i(in, out);
+}
+
+void test_reftable_basics__put_get_be16(void)
+{
+	uint32_t in = 0xfef1;
+	uint8_t dest[3];
+	uint32_t out;
+	reftable_put_be16(dest, in);
+	out = reftable_get_be16(dest);
+	cl_assert_equal_i(in, out);
+}
+
+void test_reftable_basics__grow_alloc(void)
+{
+	int *arr = NULL, *old_arr;
+	size_t alloc = 0, old_alloc;
+
+	cl_assert_equal_i(REFTABLE_ALLOC_GROW(arr, 1, alloc), 0);
+	cl_assert(arr != NULL);
+	cl_assert(alloc >= 1);
+	arr[0] = 42;
+
+	old_alloc = alloc;
+	old_arr = arr;
+	reftable_set_alloc(NULL, realloc_stub, NULL);
+	cl_assert(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
+	cl_assert(arr == old_arr);
+	cl_assert_equal_i(alloc, old_alloc);
+
+	old_alloc = alloc;
+	reftable_set_alloc(NULL, NULL, NULL);
+	cl_assert_equal_i(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc), 0);
+	cl_assert(arr != NULL);
+	cl_assert(alloc > old_alloc);
+	arr[alloc - 1] = 42;
+
+	reftable_free(arr);
+}
+
+void test_reftable_basics__grow_alloc_or_null(void)
+{
+	int *arr = NULL;
+	size_t alloc = 0, old_alloc;
+
+	REFTABLE_ALLOC_GROW_OR_NULL(arr, 1, alloc);
+	cl_assert(arr != NULL);
+	cl_assert(alloc >= 1);
+	arr[0] = 42;
+
+	old_alloc = alloc;
+	REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
+	cl_assert(arr != NULL);
+	cl_assert(alloc > old_alloc);
+	arr[alloc - 1] = 42;
+
+	old_alloc = alloc;
+	reftable_set_alloc(NULL, realloc_stub, NULL);
+	REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
+	cl_assert(arr == NULL);
+	cl_assert_equal_i(alloc, 0);
+	reftable_set_alloc(NULL, NULL, NULL);
+
+	reftable_free(arr);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
  2025-04-29 17:52 ` [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h} Seyi Kuforiji
  2025-04-29 17:52 ` [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:52 ` [PATCH v2 04/10] t/unit-tests: convert reftable merged " Seyi Kuforiji
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable block test file to use clar testing framework by using
clar assertions where necessary.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                        |   2 +-
 t/meson.build                   |   2 +-
 t/unit-tests/t-reftable-block.c | 383 --------------------------------
 t/unit-tests/u-reftable-block.c | 373 +++++++++++++++++++++++++++++++
 4 files changed, 375 insertions(+), 385 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-block.c
 create mode 100644 t/unit-tests/u-reftable-block.c

diff --git a/Makefile b/Makefile
index 7b12bb078c..239c575dad 100644
--- a/Makefile
+++ b/Makefile
@@ -1363,6 +1363,7 @@ CLAR_TEST_SUITES += u-oidmap
 CLAR_TEST_SUITES += u-oidtree
 CLAR_TEST_SUITES += u-prio-queue
 CLAR_TEST_SUITES += u-reftable-basics
+CLAR_TEST_SUITES += u-reftable-block
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1375,7 +1376,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-block
 UNIT_TEST_PROGRAMS += t-reftable-merged
 UNIT_TEST_PROGRAMS += t-reftable-pq
 UNIT_TEST_PROGRAMS += t-reftable-reader
diff --git a/t/meson.build b/t/meson.build
index 8a42b595d9..e0d723e74b 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -9,6 +9,7 @@ clar_test_suites = [
   'unit-tests/u-oidtree.c',
   'unit-tests/u-prio-queue.c',
   'unit-tests/u-reftable-basics.c',
+  'unit-tests/u-reftable-block.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -55,7 +56,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-block.c',
   'unit-tests/t-reftable-merged.c',
   'unit-tests/t-reftable-pq.c',
   'unit-tests/t-reftable-reader.c',
diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c
deleted file mode 100644
index 22040aeefa..0000000000
--- a/t/unit-tests/t-reftable-block.c
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "test-lib.h"
-#include "reftable/block.h"
-#include "reftable/blocksource.h"
-#include "reftable/constants.h"
-#include "reftable/reftable-error.h"
-#include "strbuf.h"
-
-static void t_ref_block_read_write(void)
-{
-	const int header_off = 21; /* random */
-	struct reftable_record recs[30];
-	const size_t N = ARRAY_SIZE(recs);
-	const size_t block_size = 1024;
-	struct reftable_block block = { 0 };
-	struct block_writer bw = {
-		.last_key = REFTABLE_BUF_INIT,
-	};
-	struct reftable_record rec = {
-		.type = BLOCK_TYPE_REF,
-	};
-	size_t i = 0;
-	int ret;
-	struct block_reader br = { 0 };
-	struct block_iter it = BLOCK_ITER_INIT;
-	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
-
-	REFTABLE_CALLOC_ARRAY(block.data, block_size);
-	check(block.data != NULL);
-	block.len = block_size;
-	block_source_from_buf(&block.source ,&buf);
-	ret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
-				header_off, hash_size(REFTABLE_HASH_SHA1));
-	check(!ret);
-
-	rec.u.ref.refname = (char *) "";
-	rec.u.ref.value_type = REFTABLE_REF_DELETION;
-	ret = block_writer_add(&bw, &rec);
-	check_int(ret, ==, REFTABLE_API_ERROR);
-
-	for (i = 0; i < N; i++) {
-		rec.u.ref.refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
-		rec.u.ref.value_type = REFTABLE_REF_VAL1;
-		memset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);
-
-		recs[i] = rec;
-		ret = block_writer_add(&bw, &rec);
-		rec.u.ref.refname = NULL;
-		rec.u.ref.value_type = REFTABLE_REF_DELETION;
-		check_int(ret, ==, 0);
-	}
-
-	ret = block_writer_finish(&bw);
-	check_int(ret, >, 0);
-
-	block_writer_release(&bw);
-
-	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
-
-	block_iter_seek_start(&it, &br);
-
-	for (i = 0; ; i++) {
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, >=, 0);
-		if (ret > 0) {
-			check_int(i, ==, N);
-			break;
-		}
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	for (i = 0; i < N; i++) {
-		block_iter_reset(&it);
-		reftable_record_key(&recs[i], &want);
-
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-
-		want.len--;
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-		check(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	block_reader_release(&br);
-	block_iter_close(&it);
-	reftable_record_release(&rec);
-	reftable_block_done(&br.block);
-	reftable_buf_release(&want);
-	reftable_buf_release(&buf);
-	for (i = 0; i < N; i++)
-		reftable_record_release(&recs[i]);
-}
-
-static void t_log_block_read_write(void)
-{
-	const int header_off = 21;
-	struct reftable_record recs[30];
-	const size_t N = ARRAY_SIZE(recs);
-	const size_t block_size = 2048;
-	struct reftable_block block = { 0 };
-	struct block_writer bw = {
-		.last_key = REFTABLE_BUF_INIT,
-	};
-	struct reftable_record rec = {
-		.type = BLOCK_TYPE_LOG,
-	};
-	size_t i = 0;
-	int ret;
-	struct block_reader br = { 0 };
-	struct block_iter it = BLOCK_ITER_INIT;
-	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
-
-	REFTABLE_CALLOC_ARRAY(block.data, block_size);
-	check(block.data != NULL);
-	block.len = block_size;
-	block_source_from_buf(&block.source ,&buf);
-	ret = block_writer_init(&bw, BLOCK_TYPE_LOG, block.data, block_size,
-				header_off, hash_size(REFTABLE_HASH_SHA1));
-	check(!ret);
-
-	for (i = 0; i < N; i++) {
-		rec.u.log.refname = xstrfmt("branch%02"PRIuMAX , (uintmax_t)i);
-		rec.u.log.update_index = i;
-		rec.u.log.value_type = REFTABLE_LOG_UPDATE;
-
-		recs[i] = rec;
-		ret = block_writer_add(&bw, &rec);
-		rec.u.log.refname = NULL;
-		rec.u.log.value_type = REFTABLE_LOG_DELETION;
-		check_int(ret, ==, 0);
-	}
-
-	ret = block_writer_finish(&bw);
-	check_int(ret, >, 0);
-
-	block_writer_release(&bw);
-
-	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
-
-	block_iter_seek_start(&it, &br);
-
-	for (i = 0; ; i++) {
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, >=, 0);
-		if (ret > 0) {
-			check_int(i, ==, N);
-			break;
-		}
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	for (i = 0; i < N; i++) {
-		block_iter_reset(&it);
-		reftable_buf_reset(&want);
-		check(!reftable_buf_addstr(&want, recs[i].u.log.refname));
-
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-
-		want.len--;
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-		check(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	block_reader_release(&br);
-	block_iter_close(&it);
-	reftable_record_release(&rec);
-	reftable_block_done(&br.block);
-	reftable_buf_release(&want);
-	reftable_buf_release(&buf);
-	for (i = 0; i < N; i++)
-		reftable_record_release(&recs[i]);
-}
-
-static void t_obj_block_read_write(void)
-{
-	const int header_off = 21;
-	struct reftable_record recs[30];
-	const size_t N = ARRAY_SIZE(recs);
-	const size_t block_size = 1024;
-	struct reftable_block block = { 0 };
-	struct block_writer bw = {
-		.last_key = REFTABLE_BUF_INIT,
-	};
-	struct reftable_record rec = {
-		.type = BLOCK_TYPE_OBJ,
-	};
-	size_t i = 0;
-	int ret;
-	struct block_reader br = { 0 };
-	struct block_iter it = BLOCK_ITER_INIT;
-	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
-
-	REFTABLE_CALLOC_ARRAY(block.data, block_size);
-	check(block.data != NULL);
-	block.len = block_size;
-	block_source_from_buf(&block.source, &buf);
-	ret = block_writer_init(&bw, BLOCK_TYPE_OBJ, block.data, block_size,
-				header_off, hash_size(REFTABLE_HASH_SHA1));
-	check(!ret);
-
-	for (i = 0; i < N; i++) {
-		uint8_t bytes[] = { i, i + 1, i + 2, i + 3, i + 5 }, *allocated;
-		DUP_ARRAY(allocated, bytes, ARRAY_SIZE(bytes));
-
-		rec.u.obj.hash_prefix = allocated;
-		rec.u.obj.hash_prefix_len = 5;
-
-		recs[i] = rec;
-		ret = block_writer_add(&bw, &rec);
-		rec.u.obj.hash_prefix = NULL;
-		rec.u.obj.hash_prefix_len = 0;
-		check_int(ret, ==, 0);
-	}
-
-	ret = block_writer_finish(&bw);
-	check_int(ret, >, 0);
-
-	block_writer_release(&bw);
-
-	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
-
-	block_iter_seek_start(&it, &br);
-
-	for (i = 0; ; i++) {
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, >=, 0);
-		if (ret > 0) {
-			check_int(i, ==, N);
-			break;
-		}
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	for (i = 0; i < N; i++) {
-		block_iter_reset(&it);
-		reftable_record_key(&recs[i], &want);
-
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	block_reader_release(&br);
-	block_iter_close(&it);
-	reftable_record_release(&rec);
-	reftable_block_done(&br.block);
-	reftable_buf_release(&want);
-	reftable_buf_release(&buf);
-	for (i = 0; i < N; i++)
-		reftable_record_release(&recs[i]);
-}
-
-static void t_index_block_read_write(void)
-{
-	const int header_off = 21;
-	struct reftable_record recs[30];
-	const size_t N = ARRAY_SIZE(recs);
-	const size_t block_size = 1024;
-	struct reftable_block block = { 0 };
-	struct block_writer bw = {
-		.last_key = REFTABLE_BUF_INIT,
-	};
-	struct reftable_record rec = {
-		.type = BLOCK_TYPE_INDEX,
-		.u.idx.last_key = REFTABLE_BUF_INIT,
-	};
-	size_t i = 0;
-	int ret;
-	struct block_reader br = { 0 };
-	struct block_iter it = BLOCK_ITER_INIT;
-	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
-
-	REFTABLE_CALLOC_ARRAY(block.data, block_size);
-	check(block.data != NULL);
-	block.len = block_size;
-	block_source_from_buf(&block.source, &buf);
-	ret = block_writer_init(&bw, BLOCK_TYPE_INDEX, block.data, block_size,
-				header_off, hash_size(REFTABLE_HASH_SHA1));
-	check(!ret);
-
-	for (i = 0; i < N; i++) {
-		char buf[128];
-
-		snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
-
-		reftable_buf_init(&recs[i].u.idx.last_key);
-		recs[i].type = BLOCK_TYPE_INDEX;
-		check(!reftable_buf_addstr(&recs[i].u.idx.last_key, buf));
-		recs[i].u.idx.offset = i;
-
-		ret = block_writer_add(&bw, &recs[i]);
-		check_int(ret, ==, 0);
-	}
-
-	ret = block_writer_finish(&bw);
-	check_int(ret, >, 0);
-
-	block_writer_release(&bw);
-
-	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
-
-	block_iter_seek_start(&it, &br);
-
-	for (i = 0; ; i++) {
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, >=, 0);
-		if (ret > 0) {
-			check_int(i, ==, N);
-			break;
-		}
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	for (i = 0; i < N; i++) {
-		block_iter_reset(&it);
-		reftable_record_key(&recs[i], &want);
-
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-
-		check(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1));
-
-		want.len--;
-		ret = block_iter_seek_key(&it, &br, &want);
-		check_int(ret, ==, 0);
-
-		ret = block_iter_next(&it, &rec);
-		check_int(ret, ==, 0);
-		check(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	block_reader_release(&br);
-	block_iter_close(&it);
-	reftable_record_release(&rec);
-	reftable_block_done(&br.block);
-	reftable_buf_release(&want);
-	reftable_buf_release(&buf);
-	for (i = 0; i < N; i++)
-		reftable_record_release(&recs[i]);
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_index_block_read_write(), "read-write operations on index blocks work");
-	TEST(t_log_block_read_write(), "read-write operations on log blocks work");
-	TEST(t_obj_block_read_write(), "read-write operations on obj blocks work");
-	TEST(t_ref_block_read_write(), "read-write operations on ref blocks work");
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c
new file mode 100644
index 0000000000..af24901230
--- /dev/null
+++ b/t/unit-tests/u-reftable-block.c
@@ -0,0 +1,373 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "unit-test.h"
+#include "reftable/block.h"
+#include "reftable/blocksource.h"
+#include "reftable/constants.h"
+#include "reftable/reftable-error.h"
+#include "strbuf.h"
+
+void test_reftable_block__index_read_write(void)
+{
+	const int header_off = 21; /* random */
+	struct reftable_record recs[30];
+	const size_t N = ARRAY_SIZE(recs);
+	const size_t block_size = 1024;
+	struct reftable_block block = { 0 };
+	struct block_writer bw = {
+		.last_key = REFTABLE_BUF_INIT,
+	};
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_REF,
+	};
+	size_t i = 0;
+	int ret;
+	struct block_reader br = { 0 };
+	struct block_iter it = BLOCK_ITER_INIT;
+	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
+
+	REFTABLE_CALLOC_ARRAY(block.data, block_size);
+	cl_assert(block.data != NULL);
+	block.len = block_size;
+	block_source_from_buf(&block.source ,&buf);
+	ret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
+				header_off, hash_size(REFTABLE_HASH_SHA1));
+	cl_assert(ret == 0);
+
+	rec.u.ref.refname = (char *) "";
+	rec.u.ref.value_type = REFTABLE_REF_DELETION;
+	ret = block_writer_add(&bw, &rec);
+	cl_assert_equal_i(ret, REFTABLE_API_ERROR);
+
+	for (i = 0; i < N; i++) {
+		rec.u.ref.refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
+		rec.u.ref.value_type = REFTABLE_REF_VAL1;
+		memset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);
+
+		recs[i] = rec;
+		ret = block_writer_add(&bw, &rec);
+		rec.u.ref.refname = NULL;
+		rec.u.ref.value_type = REFTABLE_REF_DELETION;
+		cl_assert_equal_i(ret, 0);
+	}
+
+	ret = block_writer_finish(&bw);
+	cl_assert(ret > 0);
+
+	block_writer_release(&bw);
+
+	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
+
+	block_iter_seek_start(&it, &br);
+
+	for (i = 0; ; i++) {
+		ret = block_iter_next(&it, &rec);
+		cl_assert(ret >= 0);
+		if (ret > 0) {
+			cl_assert_equal_i(i, N);
+			break;
+		}
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	for (i = 0; i < N; i++) {
+		block_iter_reset(&it);
+		reftable_record_key(&recs[i], &want);
+
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+
+		want.len--;
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+		cl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	block_reader_release(&br);
+	block_iter_close(&it);
+	reftable_record_release(&rec);
+	reftable_block_done(&br.block);
+	reftable_buf_release(&want);
+	reftable_buf_release(&buf);
+	for (i = 0; i < N; i++)
+		reftable_record_release(&recs[i]);
+}
+
+void test_reftable_block__log_read_write(void)
+{
+	const int header_off = 21;
+	struct reftable_record recs[30];
+	const size_t N = ARRAY_SIZE(recs);
+	const size_t block_size = 2048;
+	struct reftable_block block = { 0 };
+	struct block_writer bw = {
+		.last_key = REFTABLE_BUF_INIT,
+	};
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_LOG,
+	};
+	size_t i = 0;
+	int ret;
+	struct block_reader br = { 0 };
+	struct block_iter it = BLOCK_ITER_INIT;
+	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
+
+	REFTABLE_CALLOC_ARRAY(block.data, block_size);
+	cl_assert(block.data != NULL);
+	block.len = block_size;
+	block_source_from_buf(&block.source ,&buf);
+	ret = block_writer_init(&bw, BLOCK_TYPE_LOG, block.data, block_size,
+				header_off, hash_size(REFTABLE_HASH_SHA1));
+	cl_assert(ret == 0);
+
+	for (i = 0; i < N; i++) {
+		rec.u.log.refname = xstrfmt("branch%02"PRIuMAX , (uintmax_t)i);
+		rec.u.log.update_index = i;
+		rec.u.log.value_type = REFTABLE_LOG_UPDATE;
+
+		recs[i] = rec;
+		ret = block_writer_add(&bw, &rec);
+		rec.u.log.refname = NULL;
+		rec.u.log.value_type = REFTABLE_LOG_DELETION;
+		cl_assert_equal_i(ret, 0);
+	}
+
+	ret = block_writer_finish(&bw);
+	cl_assert(ret > 0);
+
+	block_writer_release(&bw);
+
+	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
+
+	block_iter_seek_start(&it, &br);
+
+	for (i = 0; ; i++) {
+		ret = block_iter_next(&it, &rec);
+		cl_assert(ret >= 0);
+		if (ret > 0) {
+			cl_assert_equal_i(i, N);
+			break;
+		}
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	for (i = 0; i < N; i++) {
+		block_iter_reset(&it);
+		reftable_buf_reset(&want);
+		cl_assert(reftable_buf_addstr(&want, recs[i].u.log.refname) == 0);
+
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+
+		want.len--;
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+		cl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	block_reader_release(&br);
+	block_iter_close(&it);
+	reftable_record_release(&rec);
+	reftable_block_done(&br.block);
+	reftable_buf_release(&want);
+	reftable_buf_release(&buf);
+	for (i = 0; i < N; i++)
+		reftable_record_release(&recs[i]);
+}
+
+void test_reftable_block__obj_read_write(void)
+{
+	const int header_off = 21;
+	struct reftable_record recs[30];
+	const size_t N = ARRAY_SIZE(recs);
+	const size_t block_size = 1024;
+	struct reftable_block block = { 0 };
+	struct block_writer bw = {
+		.last_key = REFTABLE_BUF_INIT,
+	};
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_OBJ,
+	};
+	size_t i = 0;
+	int ret;
+	struct block_reader br = { 0 };
+	struct block_iter it = BLOCK_ITER_INIT;
+	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
+
+	REFTABLE_CALLOC_ARRAY(block.data, block_size);
+	cl_assert(block.data != NULL);
+	block.len = block_size;
+	block_source_from_buf(&block.source, &buf);
+	ret = block_writer_init(&bw, BLOCK_TYPE_OBJ, block.data, block_size,
+				header_off, hash_size(REFTABLE_HASH_SHA1));
+	cl_assert(ret == 0);
+
+	for (i = 0; i < N; i++) {
+		uint8_t bytes[] = { i, i + 1, i + 2, i + 3, i + 5 }, *allocated;
+		DUP_ARRAY(allocated, bytes, ARRAY_SIZE(bytes));
+
+		rec.u.obj.hash_prefix = allocated;
+		rec.u.obj.hash_prefix_len = 5;
+
+		recs[i] = rec;
+		ret = block_writer_add(&bw, &rec);
+		rec.u.obj.hash_prefix = NULL;
+		rec.u.obj.hash_prefix_len = 0;
+		cl_assert_equal_i(ret, 0);
+	}
+
+	ret = block_writer_finish(&bw);
+	cl_assert(ret > 0);
+
+	block_writer_release(&bw);
+
+	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
+
+	block_iter_seek_start(&it, &br);
+
+	for (i = 0; ; i++) {
+		ret = block_iter_next(&it, &rec);
+		cl_assert(ret >= 0);
+		if (ret > 0) {
+			cl_assert_equal_i(i, N);
+			break;
+		}
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	for (i = 0; i < N; i++) {
+		block_iter_reset(&it);
+		reftable_record_key(&recs[i], &want);
+
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	block_reader_release(&br);
+	block_iter_close(&it);
+	reftable_record_release(&rec);
+	reftable_block_done(&br.block);
+	reftable_buf_release(&want);
+	reftable_buf_release(&buf);
+	for (i = 0; i < N; i++)
+		reftable_record_release(&recs[i]);
+}
+
+void test_reftable_block__ref_read_write(void)
+{
+	const int header_off = 21;
+	struct reftable_record recs[30];
+	const size_t N = ARRAY_SIZE(recs);
+	const size_t block_size = 1024;
+	struct reftable_block block = { 0 };
+	struct block_writer bw = {
+		.last_key = REFTABLE_BUF_INIT,
+	};
+	struct reftable_record rec = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx.last_key = REFTABLE_BUF_INIT,
+	};
+	size_t i = 0;
+	int ret;
+	struct block_reader br = { 0 };
+	struct block_iter it = BLOCK_ITER_INIT;
+	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
+
+	REFTABLE_CALLOC_ARRAY(block.data, block_size);
+	cl_assert(block.data != NULL);
+	block.len = block_size;
+	block_source_from_buf(&block.source, &buf);
+	ret = block_writer_init(&bw, BLOCK_TYPE_INDEX, block.data, block_size,
+				header_off, hash_size(REFTABLE_HASH_SHA1));
+	cl_assert(ret == 0);
+
+	for (i = 0; i < N; i++) {
+		char buf[128];
+
+		snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
+
+		reftable_buf_init(&recs[i].u.idx.last_key);
+		recs[i].type = BLOCK_TYPE_INDEX;
+		cl_assert(reftable_buf_addstr(&recs[i].u.idx.last_key, buf) == 0);
+		recs[i].u.idx.offset = i;
+
+		ret = block_writer_add(&bw, &recs[i]);
+		cl_assert_equal_i(ret, 0);
+	}
+
+	ret = block_writer_finish(&bw);
+	cl_assert(ret > 0);
+
+	block_writer_release(&bw);
+
+	block_reader_init(&br, &block, header_off, block_size, REFTABLE_HASH_SIZE_SHA1);
+
+	block_iter_seek_start(&it, &br);
+
+	for (i = 0; ; i++) {
+		ret = block_iter_next(&it, &rec);
+		cl_assert(ret >= 0);
+		if (ret > 0) {
+			cl_assert_equal_i(i, N);
+			break;
+		}
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	for (i = 0; i < N; i++) {
+		block_iter_reset(&it);
+		reftable_record_key(&recs[i], &want);
+
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+
+		cl_assert_equal_i(reftable_record_equal(&recs[i], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+
+		want.len--;
+		ret = block_iter_seek_key(&it, &br, &want);
+		cl_assert_equal_i(ret, 0);
+
+		ret = block_iter_next(&it, &rec);
+		cl_assert_equal_i(ret, 0);
+		cl_assert_equal_i(reftable_record_equal(&recs[10 * (i / 10)], &rec, REFTABLE_HASH_SIZE_SHA1), 1);
+	}
+
+	block_reader_release(&br);
+	block_iter_close(&it);
+	reftable_record_release(&rec);
+	reftable_block_done(&br.block);
+	reftable_buf_release(&want);
+	reftable_buf_release(&buf);
+	for (i = 0; i < N; i++)
+		reftable_record_release(&recs[i]);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 04/10] t/unit-tests: convert reftable merged test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (2 preceding siblings ...)
  2025-04-29 17:52 ` [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:52 ` [PATCH v2 05/10] t/unit-tests: convert reftable pq " Seyi Kuforiji
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable merged test file to use clar testing framework by using
clar assertions where necessary.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                         |   2 +-
 t/meson.build                    |   2 +-
 t/unit-tests/t-reftable-merged.c | 546 -------------------------------
 t/unit-tests/u-reftable-merged.c | 515 +++++++++++++++++++++++++++++
 4 files changed, 517 insertions(+), 548 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-merged.c
 create mode 100644 t/unit-tests/u-reftable-merged.c

diff --git a/Makefile b/Makefile
index 239c575dad..2b1642465a 100644
--- a/Makefile
+++ b/Makefile
@@ -1364,6 +1364,7 @@ CLAR_TEST_SUITES += u-oidtree
 CLAR_TEST_SUITES += u-prio-queue
 CLAR_TEST_SUITES += u-reftable-basics
 CLAR_TEST_SUITES += u-reftable-block
+CLAR_TEST_SUITES += u-reftable-merged
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1376,7 +1377,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-merged
 UNIT_TEST_PROGRAMS += t-reftable-pq
 UNIT_TEST_PROGRAMS += t-reftable-reader
 UNIT_TEST_PROGRAMS += t-reftable-readwrite
diff --git a/t/meson.build b/t/meson.build
index e0d723e74b..70a783ba80 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -10,6 +10,7 @@ clar_test_suites = [
   'unit-tests/u-prio-queue.c',
   'unit-tests/u-reftable-basics.c',
   'unit-tests/u-reftable-block.c',
+  'unit-tests/u-reftable-merged.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -56,7 +57,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-merged.c',
   'unit-tests/t-reftable-pq.c',
   'unit-tests/t-reftable-reader.c',
   'unit-tests/t-reftable-readwrite.c',
diff --git a/t/unit-tests/t-reftable-merged.c b/t/unit-tests/t-reftable-merged.c
deleted file mode 100644
index 60836f80d6..0000000000
--- a/t/unit-tests/t-reftable-merged.c
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "test-lib.h"
-#include "lib-reftable.h"
-#include "reftable/blocksource.h"
-#include "reftable/constants.h"
-#include "reftable/merged.h"
-#include "reftable/reader.h"
-#include "reftable/reftable-error.h"
-#include "reftable/reftable-merged.h"
-#include "reftable/reftable-writer.h"
-
-static struct reftable_merged_table *
-merged_table_from_records(struct reftable_ref_record **refs,
-			  struct reftable_block_source **source,
-			  struct reftable_reader ***readers, const size_t *sizes,
-			  struct reftable_buf *buf, const size_t n)
-{
-	struct reftable_merged_table *mt = NULL;
-	struct reftable_write_options opts = {
-		.block_size = 256,
-	};
-	int err;
-
-	REFTABLE_CALLOC_ARRAY(*readers, n);
-	check(*readers != NULL);
-	REFTABLE_CALLOC_ARRAY(*source, n);
-	check(*source != NULL);
-
-	for (size_t i = 0; i < n; i++) {
-		t_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);
-		block_source_from_buf(&(*source)[i], &buf[i]);
-
-		err = reftable_reader_new(&(*readers)[i], &(*source)[i],
-					  "name");
-		check(!err);
-	}
-
-	err = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);
-	check(!err);
-	return mt;
-}
-
-static void readers_destroy(struct reftable_reader **readers, const size_t n)
-{
-	for (size_t i = 0; i < n; i++)
-		reftable_reader_decref(readers[i]);
-	reftable_free(readers);
-}
-
-static void t_merged_single_record(void)
-{
-	struct reftable_ref_record r1[] = { {
-		.refname = (char *) "b",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_VAL1,
-		.value.val1 = { 1, 2, 3, 0 },
-	} };
-	struct reftable_ref_record r2[] = { {
-		.refname = (char *) "a",
-		.update_index = 2,
-		.value_type = REFTABLE_REF_DELETION,
-	} };
-	struct reftable_ref_record r3[] = { {
-		.refname = (char *) "c",
-		.update_index = 3,
-		.value_type = REFTABLE_REF_DELETION,
-	} };
-
-	struct reftable_ref_record *refs[] = { r1, r2, r3 };
-	size_t sizes[] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
-	struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };
-	struct reftable_block_source *bs = NULL;
-	struct reftable_reader **readers = NULL;
-	struct reftable_merged_table *mt =
-		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
-	struct reftable_ref_record ref = { 0 };
-	struct reftable_iterator it = { 0 };
-	int err;
-
-	err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, "a");
-	check(!err);
-
-	err = reftable_iterator_next_ref(&it, &ref);
-	check(!err);
-	check(reftable_ref_record_equal(&r2[0], &ref, REFTABLE_HASH_SIZE_SHA1));
-	reftable_ref_record_release(&ref);
-	reftable_iterator_destroy(&it);
-	readers_destroy(readers, 3);
-	reftable_merged_table_free(mt);
-	for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
-		reftable_buf_release(&bufs[i]);
-	reftable_free(bs);
-}
-
-static void t_merged_refs(void)
-{
-	struct reftable_ref_record r1[] = {
-		{
-			.refname = (char *) "a",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 1 },
-		},
-		{
-			.refname = (char *) "b",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 1 },
-		},
-		{
-			.refname = (char *) "c",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 1 },
-		}
-	};
-	struct reftable_ref_record r2[] = { {
-		.refname = (char *) "a",
-		.update_index = 2,
-		.value_type = REFTABLE_REF_DELETION,
-	} };
-	struct reftable_ref_record r3[] = {
-		{
-			.refname = (char *) "c",
-			.update_index = 3,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 2 },
-		},
-		{
-			.refname = (char *) "d",
-			.update_index = 3,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 1 },
-		},
-	};
-
-	struct reftable_ref_record *want[] = {
-		&r2[0],
-		&r1[1],
-		&r3[0],
-		&r3[1],
-	};
-
-	struct reftable_ref_record *refs[] = { r1, r2, r3 };
-	size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
-	struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };
-	struct reftable_block_source *bs = NULL;
-	struct reftable_reader **readers = NULL;
-	struct reftable_merged_table *mt =
-		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
-	struct reftable_iterator it = { 0 };
-	int err;
-	struct reftable_ref_record *out = NULL;
-	size_t len = 0;
-	size_t cap = 0;
-	size_t i;
-
-	err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, "a");
-	check(!err);
-	check_int(reftable_merged_table_hash_id(mt), ==, REFTABLE_HASH_SHA1);
-	check_int(reftable_merged_table_min_update_index(mt), ==, 1);
-	check_int(reftable_merged_table_max_update_index(mt), ==, 3);
-
-	while (len < 100) { /* cap loops/recursion. */
-		struct reftable_ref_record ref = { 0 };
-		int err = reftable_iterator_next_ref(&it, &ref);
-		if (err > 0)
-			break;
-
-		check(!REFTABLE_ALLOC_GROW(out, len + 1, cap));
-		out[len++] = ref;
-	}
-	reftable_iterator_destroy(&it);
-
-	check_int(ARRAY_SIZE(want), ==, len);
-	for (i = 0; i < len; i++)
-		check(reftable_ref_record_equal(want[i], &out[i],
-						 REFTABLE_HASH_SIZE_SHA1));
-	for (i = 0; i < len; i++)
-		reftable_ref_record_release(&out[i]);
-	reftable_free(out);
-
-	for (i = 0; i < 3; i++)
-		reftable_buf_release(&bufs[i]);
-	readers_destroy(readers, 3);
-	reftable_merged_table_free(mt);
-	reftable_free(bs);
-}
-
-static void t_merged_seek_multiple_times(void)
-{
-	struct reftable_ref_record r1[] = {
-		{
-			.refname = (char *) "a",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 1 },
-		},
-		{
-			.refname = (char *) "c",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 2 },
-		}
-	};
-	struct reftable_ref_record r2[] = {
-		{
-			.refname = (char *) "b",
-			.update_index = 2,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 3 },
-		},
-		{
-			.refname = (char *) "d",
-			.update_index = 2,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 4 },
-		},
-	};
-	struct reftable_ref_record *refs[] = {
-		r1, r2,
-	};
-	size_t sizes[] = {
-		ARRAY_SIZE(r1), ARRAY_SIZE(r2),
-	};
-	struct reftable_buf bufs[] = {
-		REFTABLE_BUF_INIT, REFTABLE_BUF_INIT,
-	};
-	struct reftable_block_source *sources = NULL;
-	struct reftable_reader **readers = NULL;
-	struct reftable_ref_record rec = { 0 };
-	struct reftable_iterator it = { 0 };
-	struct reftable_merged_table *mt;
-
-	mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
-	merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
-
-	for (size_t i = 0; i < 5; i++) {
-		int err = reftable_iterator_seek_ref(&it, "c");
-		check(!err);
-
-		err = reftable_iterator_next_ref(&it, &rec);
-		check(!err);
-		err = reftable_ref_record_equal(&rec, &r1[1], REFTABLE_HASH_SIZE_SHA1);
-		check(err == 1);
-
-		err = reftable_iterator_next_ref(&it, &rec);
-		check(!err);
-		err = reftable_ref_record_equal(&rec, &r2[1], REFTABLE_HASH_SIZE_SHA1);
-		check(err == 1);
-
-		err = reftable_iterator_next_ref(&it, &rec);
-		check(err > 0);
-	}
-
-	for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
-		reftable_buf_release(&bufs[i]);
-	readers_destroy(readers, ARRAY_SIZE(refs));
-	reftable_ref_record_release(&rec);
-	reftable_iterator_destroy(&it);
-	reftable_merged_table_free(mt);
-	reftable_free(sources);
-}
-
-static void t_merged_seek_multiple_times_without_draining(void)
-{
-	struct reftable_ref_record r1[] = {
-		{
-			.refname = (char *) "a",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 1 },
-		},
-		{
-			.refname = (char *) "c",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 2 },
-		}
-	};
-	struct reftable_ref_record r2[] = {
-		{
-			.refname = (char *) "b",
-			.update_index = 2,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 3 },
-		},
-		{
-			.refname = (char *) "d",
-			.update_index = 2,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 4 },
-		},
-	};
-	struct reftable_ref_record *refs[] = {
-		r1, r2,
-	};
-	size_t sizes[] = {
-		ARRAY_SIZE(r1), ARRAY_SIZE(r2),
-	};
-	struct reftable_buf bufs[] = {
-		REFTABLE_BUF_INIT, REFTABLE_BUF_INIT,
-	};
-	struct reftable_block_source *sources = NULL;
-	struct reftable_reader **readers = NULL;
-	struct reftable_ref_record rec = { 0 };
-	struct reftable_iterator it = { 0 };
-	struct reftable_merged_table *mt;
-	int err;
-
-	mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
-	merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
-
-	err = reftable_iterator_seek_ref(&it, "b");
-	check(!err);
-	err = reftable_iterator_next_ref(&it, &rec);
-	check(!err);
-	err = reftable_ref_record_equal(&rec, &r2[0], REFTABLE_HASH_SIZE_SHA1);
-	check(err == 1);
-
-	err = reftable_iterator_seek_ref(&it, "a");
-	check(!err);
-	err = reftable_iterator_next_ref(&it, &rec);
-	check(!err);
-	err = reftable_ref_record_equal(&rec, &r1[0], REFTABLE_HASH_SIZE_SHA1);
-	check(err == 1);
-
-	for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
-		reftable_buf_release(&bufs[i]);
-	readers_destroy(readers, ARRAY_SIZE(refs));
-	reftable_ref_record_release(&rec);
-	reftable_iterator_destroy(&it);
-	reftable_merged_table_free(mt);
-	reftable_free(sources);
-}
-
-static struct reftable_merged_table *
-merged_table_from_log_records(struct reftable_log_record **logs,
-			      struct reftable_block_source **source,
-			      struct reftable_reader ***readers, const size_t *sizes,
-			      struct reftable_buf *buf, const size_t n)
-{
-	struct reftable_merged_table *mt = NULL;
-	struct reftable_write_options opts = {
-		.block_size = 256,
-		.exact_log_message = 1,
-	};
-	int err;
-
-	REFTABLE_CALLOC_ARRAY(*readers, n);
-	check(*readers != NULL);
-	REFTABLE_CALLOC_ARRAY(*source, n);
-	check(*source != NULL);
-
-	for (size_t i = 0; i < n; i++) {
-		t_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts);
-		block_source_from_buf(&(*source)[i], &buf[i]);
-
-		err = reftable_reader_new(&(*readers)[i], &(*source)[i],
-					  "name");
-		check(!err);
-	}
-
-	err = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);
-	check(!err);
-	return mt;
-}
-
-static void t_merged_logs(void)
-{
-	struct reftable_log_record r1[] = {
-		{
-			.refname = (char *) "a",
-			.update_index = 2,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value.update = {
-				.old_hash = { 2 },
-				/* deletion */
-				.name = (char *) "jane doe",
-				.email = (char *) "jane@invalid",
-				.message = (char *) "message2",
-			}
-		},
-		{
-			.refname = (char *) "a",
-			.update_index = 1,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value.update = {
-				.old_hash = { 1 },
-				.new_hash = { 2 },
-				.name = (char *) "jane doe",
-				.email = (char *) "jane@invalid",
-				.message = (char *) "message1",
-			}
-		},
-	};
-	struct reftable_log_record r2[] = {
-		{
-			.refname = (char *) "a",
-			.update_index = 3,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value.update = {
-				.new_hash = { 3 },
-				.name = (char *) "jane doe",
-				.email = (char *) "jane@invalid",
-				.message = (char *) "message3",
-			}
-		},
-	};
-	struct reftable_log_record r3[] = {
-		{
-			.refname = (char *) "a",
-			.update_index = 2,
-			.value_type = REFTABLE_LOG_DELETION,
-		},
-	};
-	struct reftable_log_record *want[] = {
-		&r2[0],
-		&r3[0],
-		&r1[1],
-	};
-
-	struct reftable_log_record *logs[] = { r1, r2, r3 };
-	size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
-	struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };
-	struct reftable_block_source *bs = NULL;
-	struct reftable_reader **readers = NULL;
-	struct reftable_merged_table *mt = merged_table_from_log_records(
-		logs, &bs, &readers, sizes, bufs, 3);
-	struct reftable_iterator it = { 0 };
-	int err;
-	struct reftable_log_record *out = NULL;
-	size_t len = 0;
-	size_t cap = 0;
-	size_t i;
-
-	err = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG);
-	check(!err);
-	err = reftable_iterator_seek_log(&it, "a");
-	check(!err);
-	check_int(reftable_merged_table_hash_id(mt), ==, REFTABLE_HASH_SHA1);
-	check_int(reftable_merged_table_min_update_index(mt), ==, 1);
-	check_int(reftable_merged_table_max_update_index(mt), ==, 3);
-
-	while (len < 100) { /* cap loops/recursion. */
-		struct reftable_log_record log = { 0 };
-		int err = reftable_iterator_next_log(&it, &log);
-		if (err > 0)
-			break;
-
-		check(!REFTABLE_ALLOC_GROW(out, len + 1, cap));
-		out[len++] = log;
-	}
-	reftable_iterator_destroy(&it);
-
-	check_int(ARRAY_SIZE(want), ==, len);
-	for (i = 0; i < len; i++)
-		check(reftable_log_record_equal(want[i], &out[i],
-						 REFTABLE_HASH_SIZE_SHA1));
-
-	err = merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG);
-	check(!err);
-	err = reftable_iterator_seek_log_at(&it, "a", 2);
-	check(!err);
-	reftable_log_record_release(&out[0]);
-	err = reftable_iterator_next_log(&it, &out[0]);
-	check(!err);
-	check(reftable_log_record_equal(&out[0], &r3[0], REFTABLE_HASH_SIZE_SHA1));
-	reftable_iterator_destroy(&it);
-
-	for (i = 0; i < len; i++)
-		reftable_log_record_release(&out[i]);
-	reftable_free(out);
-
-	for (i = 0; i < 3; i++)
-		reftable_buf_release(&bufs[i]);
-	readers_destroy(readers, 3);
-	reftable_merged_table_free(mt);
-	reftable_free(bs);
-}
-
-static void t_default_write_opts(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_ref_record rec = {
-		.refname = (char *) "master",
-		.update_index = 1,
-	};
-	int err;
-	struct reftable_block_source source = { 0 };
-	uint32_t hash_id;
-	struct reftable_reader *rd = NULL;
-	struct reftable_merged_table *merged = NULL;
-
-	reftable_writer_set_limits(w, 1, 1);
-
-	err = reftable_writer_add_ref(w, &rec);
-	check(!err);
-
-	err = reftable_writer_close(w);
-	check(!err);
-	reftable_writer_free(w);
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&rd, &source, "filename");
-	check(!err);
-
-	hash_id = reftable_reader_hash_id(rd);
-	check_int(hash_id, ==, REFTABLE_HASH_SHA1);
-
-	err = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA256);
-	check_int(err, ==, REFTABLE_FORMAT_ERROR);
-	err = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA1);
-	check(!err);
-
-	reftable_reader_decref(rd);
-	reftable_merged_table_free(merged);
-	reftable_buf_release(&buf);
-}
-
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_default_write_opts(), "merged table with default write opts");
-	TEST(t_merged_logs(), "merged table with multiple log updates for same ref");
-	TEST(t_merged_refs(), "merged table with multiple updates to same ref");
-	TEST(t_merged_seek_multiple_times(), "merged table can seek multiple times");
-	TEST(t_merged_seek_multiple_times_without_draining(), "merged table can seek multiple times without draining");
-	TEST(t_merged_single_record(), "ref occurring in only one record can be fetched");
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c
new file mode 100644
index 0000000000..48c8f9f6b5
--- /dev/null
+++ b/t/unit-tests/u-reftable-merged.c
@@ -0,0 +1,515 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "unit-test.h"
+#include "lib-reftable.h"
+#include "reftable/blocksource.h"
+#include "reftable/constants.h"
+#include "reftable/merged.h"
+#include "reftable/reader.h"
+#include "reftable/reftable-error.h"
+#include "reftable/reftable-merged.h"
+#include "reftable/reftable-writer.h"
+
+static struct reftable_merged_table *
+merged_table_from_records(struct reftable_ref_record **refs,
+			  struct reftable_block_source **source,
+			  struct reftable_reader ***readers, const size_t *sizes,
+			  struct reftable_buf *buf, const size_t n)
+{
+	struct reftable_merged_table *mt = NULL;
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	int err;
+
+	REFTABLE_CALLOC_ARRAY(*readers, n);
+	cl_assert(*readers != NULL);
+	REFTABLE_CALLOC_ARRAY(*source, n);
+	cl_assert(*source != NULL);
+
+	for (size_t i = 0; i < n; i++) {
+		cl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);
+		block_source_from_buf(&(*source)[i], &buf[i]);
+
+		err = reftable_reader_new(&(*readers)[i], &(*source)[i],
+					  "name");
+		cl_assert(err == 0);
+	}
+
+	err = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);
+	cl_assert(err == 0);
+	return mt;
+}
+
+static void readers_destroy(struct reftable_reader **readers, const size_t n)
+{
+	for (size_t i = 0; i < n; i++)
+		reftable_reader_decref(readers[i]);
+	reftable_free(readers);
+}
+
+void test_reftable_merged__merged_single_record(void)
+{
+	struct reftable_ref_record r1[] = { {
+		.refname = (char *) "b",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_VAL1,
+		.value.val1 = { 1, 2, 3, 0 },
+	} };
+	struct reftable_ref_record r2[] = { {
+		.refname = (char *) "a",
+		.update_index = 2,
+		.value_type = REFTABLE_REF_DELETION,
+	} };
+	struct reftable_ref_record r3[] = { {
+		.refname = (char *) "c",
+		.update_index = 3,
+		.value_type = REFTABLE_REF_DELETION,
+	} };
+
+	struct reftable_ref_record *refs[] = { r1, r2, r3 };
+	size_t sizes[] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
+	struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };
+	struct reftable_block_source *bs = NULL;
+	struct reftable_reader **readers = NULL;
+	struct reftable_merged_table *mt =
+		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
+	struct reftable_ref_record ref = { 0 };
+	struct reftable_iterator it = { 0 };
+	int err;
+
+	err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+	cl_assert(err == 0);
+	err = reftable_iterator_seek_ref(&it, "a");
+	cl_assert(err == 0);
+
+	err = reftable_iterator_next_ref(&it, &ref);
+	cl_assert(err == 0);
+	cl_assert(reftable_ref_record_equal(&r2[0], &ref, REFTABLE_HASH_SIZE_SHA1) != 0);
+	reftable_ref_record_release(&ref);
+	reftable_iterator_destroy(&it);
+	readers_destroy(readers, 3);
+	reftable_merged_table_free(mt);
+	for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
+		reftable_buf_release(&bufs[i]);
+	reftable_free(bs);
+}
+
+void test_reftable_merged__merged_refs(void)
+{
+	struct reftable_ref_record r1[] = {
+		{
+			.refname = (char *) "a",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 1 },
+		},
+		{
+			.refname = (char *) "b",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 1 },
+		},
+		{
+			.refname = (char *) "c",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 1 },
+		}
+	};
+	struct reftable_ref_record r2[] = { {
+		.refname = (char *) "a",
+		.update_index = 2,
+		.value_type = REFTABLE_REF_DELETION,
+	} };
+	struct reftable_ref_record r3[] = {
+		{
+			.refname = (char *) "c",
+			.update_index = 3,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 2 },
+		},
+		{
+			.refname = (char *) "d",
+			.update_index = 3,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 1 },
+		},
+	};
+
+	struct reftable_ref_record *want[] = {
+		&r2[0],
+		&r1[1],
+		&r3[0],
+		&r3[1],
+	};
+
+	struct reftable_ref_record *refs[] = { r1, r2, r3 };
+	size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
+	struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };
+	struct reftable_block_source *bs = NULL;
+	struct reftable_reader **readers = NULL;
+	struct reftable_merged_table *mt =
+		merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
+	struct reftable_iterator it = { 0 };
+	int err;
+	struct reftable_ref_record *out = NULL;
+	size_t len = 0;
+	size_t cap = 0;
+	size_t i;
+
+	err = merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+	cl_assert(err == 0);
+	err = reftable_iterator_seek_ref(&it, "a");
+	cl_assert(err == 0);
+	cl_assert_equal_i(reftable_merged_table_hash_id(mt), REFTABLE_HASH_SHA1);
+	cl_assert_equal_i(reftable_merged_table_min_update_index(mt), 1);
+	cl_assert_equal_i(reftable_merged_table_max_update_index(mt), 3);
+
+	while (len < 100) { /* cap loops/recursion. */
+		struct reftable_ref_record ref = { 0 };
+		int err = reftable_iterator_next_ref(&it, &ref);
+		if (err > 0)
+			break;
+
+		cl_assert(REFTABLE_ALLOC_GROW(out, len + 1, cap) == 0);
+		out[len++] = ref;
+	}
+	reftable_iterator_destroy(&it);
+
+	cl_assert_equal_i(ARRAY_SIZE(want), len);
+	for (i = 0; i < len; i++)
+		cl_assert(reftable_ref_record_equal(want[i], &out[i],
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+	for (i = 0; i < len; i++)
+		reftable_ref_record_release(&out[i]);
+	reftable_free(out);
+
+	for (i = 0; i < 3; i++)
+		reftable_buf_release(&bufs[i]);
+	readers_destroy(readers, 3);
+	reftable_merged_table_free(mt);
+	reftable_free(bs);
+}
+
+void test_reftable_merged__merged_seek_multiple_times(void)
+{
+	struct reftable_ref_record r1[] = {
+		{
+			.refname = (char *) "a",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 1 },
+		},
+		{
+			.refname = (char *) "c",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 2 },
+		}
+	};
+	struct reftable_ref_record r2[] = {
+		{
+			.refname = (char *) "b",
+			.update_index = 2,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 3 },
+		},
+		{
+			.refname = (char *) "d",
+			.update_index = 2,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 4 },
+		},
+	};
+	struct reftable_ref_record *refs[] = {
+		r1, r2,
+	};
+	size_t sizes[] = {
+		ARRAY_SIZE(r1), ARRAY_SIZE(r2),
+	};
+	struct reftable_buf bufs[] = {
+		REFTABLE_BUF_INIT, REFTABLE_BUF_INIT,
+	};
+	struct reftable_block_source *sources = NULL;
+	struct reftable_reader **readers = NULL;
+	struct reftable_ref_record rec = { 0 };
+	struct reftable_iterator it = { 0 };
+	struct reftable_merged_table *mt;
+
+	mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
+	merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+
+	for (size_t i = 0; i < 5; i++) {
+		int err = reftable_iterator_seek_ref(&it, "c");
+		cl_assert(err == 0);
+
+		cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+		cl_assert_equal_i(reftable_ref_record_equal(&rec, &r1[1],
+													REFTABLE_HASH_SIZE_SHA1), 1);
+
+		cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+		cl_assert_equal_i(reftable_ref_record_equal(&rec, &r2[1],
+													REFTABLE_HASH_SIZE_SHA1), 1);
+
+		cl_assert(reftable_iterator_next_ref(&it, &rec) > 0);
+	}
+
+	for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
+		reftable_buf_release(&bufs[i]);
+	readers_destroy(readers, ARRAY_SIZE(refs));
+	reftable_ref_record_release(&rec);
+	reftable_iterator_destroy(&it);
+	reftable_merged_table_free(mt);
+	reftable_free(sources);
+}
+
+void test_reftable_merged__merged_seek_multiple_times_no_drain(void)
+{
+	struct reftable_ref_record r1[] = {
+		{
+			.refname = (char *) "a",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 1 },
+		},
+		{
+			.refname = (char *) "c",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 2 },
+		}
+	};
+	struct reftable_ref_record r2[] = {
+		{
+			.refname = (char *) "b",
+			.update_index = 2,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 3 },
+		},
+		{
+			.refname = (char *) "d",
+			.update_index = 2,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 4 },
+		},
+	};
+	struct reftable_ref_record *refs[] = {
+		r1, r2,
+	};
+	size_t sizes[] = {
+		ARRAY_SIZE(r1), ARRAY_SIZE(r2),
+	};
+	struct reftable_buf bufs[] = {
+		REFTABLE_BUF_INIT, REFTABLE_BUF_INIT,
+	};
+	struct reftable_block_source *sources = NULL;
+	struct reftable_reader **readers = NULL;
+	struct reftable_ref_record rec = { 0 };
+	struct reftable_iterator it = { 0 };
+	struct reftable_merged_table *mt;
+
+	mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
+	merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
+
+	cl_assert(reftable_iterator_seek_ref(&it, "b") == 0);
+	cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+	cl_assert_equal_i(reftable_ref_record_equal(&rec, &r2[0],
+												REFTABLE_HASH_SIZE_SHA1), 1);
+
+	cl_assert(reftable_iterator_seek_ref(&it, "a") == 0);
+	cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+	cl_assert_equal_i(reftable_ref_record_equal(&rec, &r1[0],
+												REFTABLE_HASH_SIZE_SHA1), 1);
+
+	for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
+		reftable_buf_release(&bufs[i]);
+	readers_destroy(readers, ARRAY_SIZE(refs));
+	reftable_ref_record_release(&rec);
+	reftable_iterator_destroy(&it);
+	reftable_merged_table_free(mt);
+	reftable_free(sources);
+}
+
+static struct reftable_merged_table *
+merged_table_from_log_records(struct reftable_log_record **logs,
+			      struct reftable_block_source **source,
+			      struct reftable_reader ***readers, const size_t *sizes,
+			      struct reftable_buf *buf, const size_t n)
+{
+	struct reftable_merged_table *mt = NULL;
+	struct reftable_write_options opts = {
+		.block_size = 256,
+		.exact_log_message = 1,
+	};
+	int err;
+
+	REFTABLE_CALLOC_ARRAY(*readers, n);
+	cl_assert(*readers != NULL);
+	REFTABLE_CALLOC_ARRAY(*source, n);
+	cl_assert(*source != NULL);
+
+	for (size_t i = 0; i < n; i++) {
+		cl_reftable_write_to_buf(&buf[i], NULL, 0, logs[i], sizes[i], &opts);
+		block_source_from_buf(&(*source)[i], &buf[i]);
+
+		err = reftable_reader_new(&(*readers)[i], &(*source)[i],
+					  "name");
+		cl_assert(err == 0);
+	}
+
+	cl_assert(reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1) == 0);
+	return mt;
+}
+
+void test_reftable_merged__merged_logs(void)
+{
+	struct reftable_log_record r1[] = {
+		{
+			.refname = (char *) "a",
+			.update_index = 2,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value.update = {
+				.old_hash = { 2 },
+				/* deletion */
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message2",
+			}
+		},
+		{
+			.refname = (char *) "a",
+			.update_index = 1,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value.update = {
+				.old_hash = { 1 },
+				.new_hash = { 2 },
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message1",
+			}
+		},
+	};
+	struct reftable_log_record r2[] = {
+		{
+			.refname = (char *) "a",
+			.update_index = 3,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value.update = {
+				.new_hash = { 3 },
+				.name = (char *) "jane doe",
+				.email = (char *) "jane@invalid",
+				.message = (char *) "message3",
+			}
+		},
+	};
+	struct reftable_log_record r3[] = {
+		{
+			.refname = (char *) "a",
+			.update_index = 2,
+			.value_type = REFTABLE_LOG_DELETION,
+		},
+	};
+	struct reftable_log_record *want[] = {
+		&r2[0],
+		&r3[0],
+		&r1[1],
+	};
+
+	struct reftable_log_record *logs[] = { r1, r2, r3 };
+	size_t sizes[3] = { ARRAY_SIZE(r1), ARRAY_SIZE(r2), ARRAY_SIZE(r3) };
+	struct reftable_buf bufs[3] = { REFTABLE_BUF_INIT, REFTABLE_BUF_INIT, REFTABLE_BUF_INIT };
+	struct reftable_block_source *bs = NULL;
+	struct reftable_reader **readers = NULL;
+	struct reftable_merged_table *mt = merged_table_from_log_records(
+		logs, &bs, &readers, sizes, bufs, 3);
+	struct reftable_iterator it = { 0 };
+	struct reftable_log_record *out = NULL;
+	size_t len = 0;
+	size_t cap = 0;
+	size_t i;
+
+	cl_assert(merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG) == 0);
+	cl_assert(reftable_iterator_seek_log(&it, "a") == 0);
+	cl_assert_equal_i(reftable_merged_table_hash_id(mt), REFTABLE_HASH_SHA1);
+	cl_assert_equal_i(reftable_merged_table_min_update_index(mt), 1);
+	cl_assert_equal_i(reftable_merged_table_max_update_index(mt), 3);
+
+	while (len < 100) { /* cap loops/recursion. */
+		struct reftable_log_record log = { 0 };
+		int err = reftable_iterator_next_log(&it, &log);
+		if (err > 0)
+			break;
+
+		cl_assert(REFTABLE_ALLOC_GROW(out, len + 1, cap) == 0);
+		out[len++] = log;
+	}
+	reftable_iterator_destroy(&it);
+
+	cl_assert_equal_i(ARRAY_SIZE(want), len);
+	for (i = 0; i < len; i++)
+		cl_assert(reftable_log_record_equal(want[i], &out[i],
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+
+	cl_assert(merged_table_init_iter(mt, &it, BLOCK_TYPE_LOG) == 0);
+	cl_assert(reftable_iterator_seek_log_at(&it, "a", 2) == 0);
+	reftable_log_record_release(&out[0]);
+	cl_assert(reftable_iterator_next_log(&it, &out[0]) == 0);
+	cl_assert(reftable_log_record_equal(&out[0], &r3[0],
+										REFTABLE_HASH_SIZE_SHA1) != 0);
+	reftable_iterator_destroy(&it);
+
+	for (i = 0; i < len; i++)
+		reftable_log_record_release(&out[i]);
+	reftable_free(out);
+
+	for (i = 0; i < 3; i++)
+		reftable_buf_release(&bufs[i]);
+	readers_destroy(readers, 3);
+	reftable_merged_table_free(mt);
+	reftable_free(bs);
+}
+
+void test_reftable_merged__default_write_opts(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_ref_record rec = {
+		.refname = (char *) "master",
+		.update_index = 1,
+	};
+	int err;
+	struct reftable_block_source source = { 0 };
+	uint32_t hash_id;
+	struct reftable_reader *rd = NULL;
+	struct reftable_merged_table *merged = NULL;
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	cl_assert(reftable_writer_add_ref(w, &rec) == 0);
+
+	cl_assert(reftable_writer_close(w) == 0);
+	reftable_writer_free(w);
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&rd, &source, "filename") == 0);
+
+	hash_id = reftable_reader_hash_id(rd);
+	cl_assert_equal_i(hash_id, REFTABLE_HASH_SHA1);
+
+	err = reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA256);
+	cl_assert_equal_i(err, REFTABLE_FORMAT_ERROR);
+	cl_assert(reftable_merged_table_new(&merged, &rd, 1, REFTABLE_HASH_SHA1) == 0);
+
+	reftable_reader_decref(rd);
+	reftable_merged_table_free(merged);
+	reftable_buf_release(&buf);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 05/10] t/unit-tests: convert reftable pq test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (3 preceding siblings ...)
  2025-04-29 17:52 ` [PATCH v2 04/10] t/unit-tests: convert reftable merged " Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-04-29 17:52 ` [PATCH v2 06/10] t/unit-tests: convert reftable reader " Seyi Kuforiji
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable priority queue test file to use clar by using clar
assertions where necessary.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                     |   2 +-
 t/meson.build                |   2 +-
 t/unit-tests/t-reftable-pq.c | 161 -----------------------------------
 t/unit-tests/u-reftable-pq.c | 152 +++++++++++++++++++++++++++++++++
 4 files changed, 154 insertions(+), 163 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-pq.c
 create mode 100644 t/unit-tests/u-reftable-pq.c

diff --git a/Makefile b/Makefile
index 2b1642465a..4142927d0a 100644
--- a/Makefile
+++ b/Makefile
@@ -1365,6 +1365,7 @@ CLAR_TEST_SUITES += u-prio-queue
 CLAR_TEST_SUITES += u-reftable-basics
 CLAR_TEST_SUITES += u-reftable-block
 CLAR_TEST_SUITES += u-reftable-merged
+CLAR_TEST_SUITES += u-reftable-pq
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1377,7 +1378,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-pq
 UNIT_TEST_PROGRAMS += t-reftable-reader
 UNIT_TEST_PROGRAMS += t-reftable-readwrite
 UNIT_TEST_PROGRAMS += t-reftable-record
diff --git a/t/meson.build b/t/meson.build
index 70a783ba80..9bded2d15c 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -11,6 +11,7 @@ clar_test_suites = [
   'unit-tests/u-reftable-basics.c',
   'unit-tests/u-reftable-block.c',
   'unit-tests/u-reftable-merged.c',
+  'unit-tests/u-reftable-pq.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -57,7 +58,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-pq.c',
   'unit-tests/t-reftable-reader.c',
   'unit-tests/t-reftable-readwrite.c',
   'unit-tests/t-reftable-record.c',
diff --git a/t/unit-tests/t-reftable-pq.c b/t/unit-tests/t-reftable-pq.c
deleted file mode 100644
index c128fe8616..0000000000
--- a/t/unit-tests/t-reftable-pq.c
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "test-lib.h"
-#include "reftable/constants.h"
-#include "reftable/pq.h"
-#include "strbuf.h"
-
-static void merged_iter_pqueue_check(const struct merged_iter_pqueue *pq)
-{
-	for (size_t i = 1; i < pq->len; i++) {
-		size_t parent = (i - 1) / 2;
-		check(pq_less(&pq->heap[parent], &pq->heap[i]));
-	}
-}
-
-static int pq_entry_equal(struct pq_entry *a, struct pq_entry *b)
-{
-	int cmp;
-	check(!reftable_record_cmp(a->rec, b->rec, &cmp));
-	return !cmp && (a->index == b->index);
-}
-
-static void t_pq_record(void)
-{
-	struct merged_iter_pqueue pq = { 0 };
-	struct reftable_record recs[54];
-	size_t N = ARRAY_SIZE(recs) - 1, i;
-	char *last = NULL;
-
-	for (i = 0; i < N; i++) {
-		check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF));
-		recs[i].u.ref.refname = xstrfmt("%02"PRIuMAX, (uintmax_t)i);
-	}
-
-	i = 1;
-	do {
-		struct pq_entry e = {
-			.rec = &recs[i],
-		};
-
-		merged_iter_pqueue_add(&pq, &e);
-		merged_iter_pqueue_check(&pq);
-		i = (i * 7) % N;
-	} while (i != 1);
-
-	while (!merged_iter_pqueue_is_empty(pq)) {
-		struct pq_entry top = merged_iter_pqueue_top(pq);
-		struct pq_entry e;
-
-		check(!merged_iter_pqueue_remove(&pq, &e));
-		merged_iter_pqueue_check(&pq);
-
-		check(pq_entry_equal(&top, &e));
-		check(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
-		if (last)
-			check_int(strcmp(last, e.rec->u.ref.refname), <, 0);
-		last = e.rec->u.ref.refname;
-	}
-
-	for (i = 0; i < N; i++)
-		reftable_record_release(&recs[i]);
-	merged_iter_pqueue_release(&pq);
-}
-
-static void t_pq_index(void)
-{
-	struct merged_iter_pqueue pq = { 0 };
-	struct reftable_record recs[13];
-	char *last = NULL;
-	size_t N = ARRAY_SIZE(recs), i;
-
-	for (i = 0; i < N; i++) {
-		check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF));
-		recs[i].u.ref.refname = (char *) "refs/heads/master";
-	}
-
-	i = 1;
-	do {
-		struct pq_entry e = {
-			.rec = &recs[i],
-			.index = i,
-		};
-
-		merged_iter_pqueue_add(&pq, &e);
-		merged_iter_pqueue_check(&pq);
-		i = (i * 7) % N;
-	} while (i != 1);
-
-	for (i = N - 1; i > 0; i--) {
-		struct pq_entry top = merged_iter_pqueue_top(pq);
-		struct pq_entry e;
-
-		check(!merged_iter_pqueue_remove(&pq, &e));
-		merged_iter_pqueue_check(&pq);
-
-		check(pq_entry_equal(&top, &e));
-		check(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
-		check_int(e.index, ==, i);
-		if (last)
-			check_str(last, e.rec->u.ref.refname);
-		last = e.rec->u.ref.refname;
-	}
-
-	merged_iter_pqueue_release(&pq);
-}
-
-static void t_merged_iter_pqueue_top(void)
-{
-	struct merged_iter_pqueue pq = { 0 };
-	struct reftable_record recs[13];
-	size_t N = ARRAY_SIZE(recs), i;
-
-	for (i = 0; i < N; i++) {
-		check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF));
-		recs[i].u.ref.refname = (char *) "refs/heads/master";
-	}
-
-	i = 1;
-	do {
-		struct pq_entry e = {
-			.rec = &recs[i],
-			.index = i,
-		};
-
-		merged_iter_pqueue_add(&pq, &e);
-		merged_iter_pqueue_check(&pq);
-		i = (i * 7) % N;
-	} while (i != 1);
-
-	for (i = N - 1; i > 0; i--) {
-		struct pq_entry top = merged_iter_pqueue_top(pq);
-		struct pq_entry e;
-
-		check(!merged_iter_pqueue_remove(&pq, &e));
-
-		merged_iter_pqueue_check(&pq);
-		check(pq_entry_equal(&top, &e));
-		check(reftable_record_equal(top.rec, &recs[i], REFTABLE_HASH_SIZE_SHA1));
-		for (size_t j = 0; i < pq.len; j++) {
-			check(pq_less(&top, &pq.heap[j]));
-			check_int(top.index, >, j);
-		}
-	}
-
-	merged_iter_pqueue_release(&pq);
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_pq_record(), "pq works with record-based comparison");
-	TEST(t_pq_index(), "pq works with index-based comparison");
-	TEST(t_merged_iter_pqueue_top(), "merged_iter_pqueue_top works");
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-pq.c b/t/unit-tests/u-reftable-pq.c
new file mode 100644
index 0000000000..ecbf08586c
--- /dev/null
+++ b/t/unit-tests/u-reftable-pq.c
@@ -0,0 +1,152 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "unit-test.h"
+#include "reftable/constants.h"
+#include "reftable/pq.h"
+#include "strbuf.h"
+
+static void merged_iter_pqueue_check(const struct merged_iter_pqueue *pq)
+{
+	for (size_t i = 1; i < pq->len; i++) {
+		size_t parent = (i - 1) / 2;
+		cl_assert(pq_less(&pq->heap[parent], &pq->heap[i]) != 0);
+	}
+}
+
+static int pq_entry_equal(struct pq_entry *a, struct pq_entry *b)
+{
+	int cmp;
+	cl_assert(reftable_record_cmp(a->rec, b->rec, &cmp) == 0);
+	return !cmp && (a->index == b->index);
+}
+
+void test_reftable_pq__record(void)
+{
+	struct merged_iter_pqueue pq = { 0 };
+	struct reftable_record recs[54];
+	size_t N = ARRAY_SIZE(recs) - 1, i;
+	char *last = NULL;
+
+	for (i = 0; i < N; i++) {
+		cl_assert(reftable_record_init(&recs[i], BLOCK_TYPE_REF) == 0);
+		recs[i].u.ref.refname = xstrfmt("%02"PRIuMAX, (uintmax_t)i);
+	}
+
+	i = 1;
+	do {
+		struct pq_entry e = {
+			.rec = &recs[i],
+		};
+
+		merged_iter_pqueue_add(&pq, &e);
+		merged_iter_pqueue_check(&pq);
+		i = (i * 7) % N;
+	} while (i != 1);
+
+	while (!merged_iter_pqueue_is_empty(pq)) {
+		struct pq_entry top = merged_iter_pqueue_top(pq);
+		struct pq_entry e;
+
+		cl_assert(merged_iter_pqueue_remove(&pq, &e) == 0);
+		merged_iter_pqueue_check(&pq);
+
+		cl_assert(pq_entry_equal(&top, &e) != 0);
+		cl_assert(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
+		if (last)
+			cl_assert(strcmp(last, e.rec->u.ref.refname) < 0);
+		last = e.rec->u.ref.refname;
+	}
+
+	for (i = 0; i < N; i++)
+		reftable_record_release(&recs[i]);
+	merged_iter_pqueue_release(&pq);
+}
+
+void test_reftable_pq__index(void)
+{
+	struct merged_iter_pqueue pq = { 0 };
+	struct reftable_record recs[13];
+	char *last = NULL;
+	size_t N = ARRAY_SIZE(recs), i;
+
+	for (i = 0; i < N; i++) {
+		cl_assert(reftable_record_init(&recs[i], BLOCK_TYPE_REF) == 0);
+		recs[i].u.ref.refname = (char *) "refs/heads/master";
+	}
+
+	i = 1;
+	do {
+		struct pq_entry e = {
+			.rec = &recs[i],
+			.index = i,
+		};
+
+		merged_iter_pqueue_add(&pq, &e);
+		merged_iter_pqueue_check(&pq);
+		i = (i * 7) % N;
+	} while (i != 1);
+
+	for (i = N - 1; i > 0; i--) {
+		struct pq_entry top = merged_iter_pqueue_top(pq);
+		struct pq_entry e;
+
+		cl_assert(merged_iter_pqueue_remove(&pq, &e) == 0);
+		merged_iter_pqueue_check(&pq);
+
+		cl_assert(pq_entry_equal(&top, &e) != 0);
+		cl_assert(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
+		cl_assert_equal_i(e.index, i);
+		if (last)
+			cl_assert_equal_s(last, e.rec->u.ref.refname);
+		last = e.rec->u.ref.refname;
+	}
+
+	merged_iter_pqueue_release(&pq);
+}
+
+void test_reftable_pq__merged_iter_pqueue_top(void)
+{
+	struct merged_iter_pqueue pq = { 0 };
+	struct reftable_record recs[13];
+	size_t N = ARRAY_SIZE(recs), i;
+
+	for (i = 0; i < N; i++) {
+		cl_assert(reftable_record_init(&recs[i], BLOCK_TYPE_REF) == 0);
+		recs[i].u.ref.refname = (char *) "refs/heads/master";
+	}
+
+	i = 1;
+	do {
+		struct pq_entry e = {
+			.rec = &recs[i],
+			.index = i,
+		};
+
+		merged_iter_pqueue_add(&pq, &e);
+		merged_iter_pqueue_check(&pq);
+		i = (i * 7) % N;
+	} while (i != 1);
+
+	for (i = N - 1; i > 0; i--) {
+		struct pq_entry top = merged_iter_pqueue_top(pq);
+		struct pq_entry e;
+
+		cl_assert(merged_iter_pqueue_remove(&pq, &e) == 0);
+
+		merged_iter_pqueue_check(&pq);
+		cl_assert(pq_entry_equal(&top, &e) != 0);
+		cl_assert(reftable_record_equal(top.rec, &recs[i], REFTABLE_HASH_SIZE_SHA1) != 0);
+		for (size_t j = 0; i < pq.len; j++) {
+			cl_assert(pq_less(&top, &pq.heap[j]) != 0);
+			cl_assert(top.index > j);
+		}
+	}
+
+	merged_iter_pqueue_release(&pq);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 06/10] t/unit-tests: convert reftable reader test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (4 preceding siblings ...)
  2025-04-29 17:52 ` [PATCH v2 05/10] t/unit-tests: convert reftable pq " Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:52 ` [PATCH v2 07/10] t/unit-tests: convert reftable readwrite " Seyi Kuforiji
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable reader test file to use clar by using clar assertions
where necessary.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                         |  2 +-
 t/meson.build                    |  2 +-
 t/unit-tests/t-reftable-reader.c | 96 --------------------------------
 t/unit-tests/u-reftable-reader.c | 78 ++++++++++++++++++++++++++
 4 files changed, 80 insertions(+), 98 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-reader.c
 create mode 100644 t/unit-tests/u-reftable-reader.c

diff --git a/Makefile b/Makefile
index 4142927d0a..d3e8677653 100644
--- a/Makefile
+++ b/Makefile
@@ -1366,6 +1366,7 @@ CLAR_TEST_SUITES += u-reftable-basics
 CLAR_TEST_SUITES += u-reftable-block
 CLAR_TEST_SUITES += u-reftable-merged
 CLAR_TEST_SUITES += u-reftable-pq
+CLAR_TEST_SUITES += u-reftable-reader
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1378,7 +1379,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-reader
 UNIT_TEST_PROGRAMS += t-reftable-readwrite
 UNIT_TEST_PROGRAMS += t-reftable-record
 UNIT_TEST_PROGRAMS += t-reftable-stack
diff --git a/t/meson.build b/t/meson.build
index 9bded2d15c..6a22bd2790 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -12,6 +12,7 @@ clar_test_suites = [
   'unit-tests/u-reftable-block.c',
   'unit-tests/u-reftable-merged.c',
   'unit-tests/u-reftable-pq.c',
+  'unit-tests/u-reftable-reader.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -58,7 +59,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-reader.c',
   'unit-tests/t-reftable-readwrite.c',
   'unit-tests/t-reftable-record.c',
   'unit-tests/t-reftable-stack.c',
diff --git a/t/unit-tests/t-reftable-reader.c b/t/unit-tests/t-reftable-reader.c
deleted file mode 100644
index 546df6005e..0000000000
--- a/t/unit-tests/t-reftable-reader.c
+++ /dev/null
@@ -1,96 +0,0 @@
-#include "test-lib.h"
-#include "lib-reftable.h"
-#include "reftable/blocksource.h"
-#include "reftable/reader.h"
-
-static int t_reader_seek_once(void)
-{
-	struct reftable_ref_record records[] = {
-		{
-			.refname = (char *) "refs/heads/main",
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 42 },
-		},
-	};
-	struct reftable_block_source source = { 0 };
-	struct reftable_ref_record ref = { 0 };
-	struct reftable_iterator it = { 0 };
-	struct reftable_reader *reader;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	int ret;
-
-	t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
-	block_source_from_buf(&source, &buf);
-
-	ret = reftable_reader_new(&reader, &source, "name");
-	check(!ret);
-
-	reftable_reader_init_ref_iterator(reader, &it);
-	ret = reftable_iterator_seek_ref(&it, "");
-	check(!ret);
-	ret = reftable_iterator_next_ref(&it, &ref);
-	check(!ret);
-
-	ret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1);
-	check_int(ret, ==, 1);
-
-	ret = reftable_iterator_next_ref(&it, &ref);
-	check_int(ret, ==, 1);
-
-	reftable_ref_record_release(&ref);
-	reftable_iterator_destroy(&it);
-	reftable_reader_decref(reader);
-	reftable_buf_release(&buf);
-	return 0;
-}
-
-static int t_reader_reseek(void)
-{
-	struct reftable_ref_record records[] = {
-		{
-			.refname = (char *) "refs/heads/main",
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { 42 },
-		},
-	};
-	struct reftable_block_source source = { 0 };
-	struct reftable_ref_record ref = { 0 };
-	struct reftable_iterator it = { 0 };
-	struct reftable_reader *reader;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	int ret;
-
-	t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
-	block_source_from_buf(&source, &buf);
-
-	ret = reftable_reader_new(&reader, &source, "name");
-	check(!ret);
-
-	reftable_reader_init_ref_iterator(reader, &it);
-
-	for (size_t i = 0; i < 5; i++) {
-		ret = reftable_iterator_seek_ref(&it, "");
-		check(!ret);
-		ret = reftable_iterator_next_ref(&it, &ref);
-		check(!ret);
-
-		ret = reftable_ref_record_equal(&ref, &records[0], REFTABLE_HASH_SIZE_SHA1);
-		check_int(ret, ==, 1);
-
-		ret = reftable_iterator_next_ref(&it, &ref);
-		check_int(ret, ==, 1);
-	}
-
-	reftable_ref_record_release(&ref);
-	reftable_iterator_destroy(&it);
-	reftable_reader_decref(reader);
-	reftable_buf_release(&buf);
-	return 0;
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_reader_seek_once(), "reader can seek once");
-	TEST(t_reader_reseek(), "reader can reseek multiple times");
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-reader.c b/t/unit-tests/u-reftable-reader.c
new file mode 100644
index 0000000000..6c35063105
--- /dev/null
+++ b/t/unit-tests/u-reftable-reader.c
@@ -0,0 +1,78 @@
+#include "unit-test.h"
+#include "lib-reftable.h"
+#include "reftable/blocksource.h"
+#include "reftable/reader.h"
+
+void test_reftable_reader__seek_once(void)
+{
+	struct reftable_ref_record records[] = {
+		{
+			.refname = (char *) "refs/heads/main",
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 42 },
+		},
+	};
+	struct reftable_block_source source = { 0 };
+	struct reftable_ref_record ref = { 0 };
+	struct reftable_iterator it = { 0 };
+	struct reftable_reader *reader;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+
+	cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "name") == 0);
+
+
+	reftable_reader_init_ref_iterator(reader, &it);
+	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+	cl_assert(reftable_iterator_next_ref(&it, &ref) == 0);
+
+	cl_assert_equal_i(reftable_ref_record_equal(&ref, &records[0],
+												REFTABLE_HASH_SIZE_SHA1), 1);
+
+	cl_assert_equal_i(reftable_iterator_next_ref(&it, &ref), 1);
+
+	reftable_ref_record_release(&ref);
+	reftable_iterator_destroy(&it);
+	reftable_reader_decref(reader);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_reader__reseek(void)
+{
+	struct reftable_ref_record records[] = {
+		{
+			.refname = (char *) "refs/heads/main",
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { 42 },
+		},
+	};
+	struct reftable_block_source source = { 0 };
+	struct reftable_ref_record ref = { 0 };
+	struct reftable_iterator it = { 0 };
+	struct reftable_reader *reader;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+
+	cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "name") == 0);
+
+	reftable_reader_init_ref_iterator(reader, &it);
+
+	for (size_t i = 0; i < 5; i++) {
+		cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+		cl_assert(reftable_iterator_next_ref(&it, &ref) == 0);
+
+		cl_assert_equal_i(reftable_ref_record_equal(&ref, &records[0],
+													REFTABLE_HASH_SIZE_SHA1), 1);
+
+		cl_assert_equal_i(reftable_iterator_next_ref(&it, &ref), 1);
+	}
+
+	reftable_ref_record_release(&ref);
+	reftable_iterator_destroy(&it);
+	reftable_reader_decref(reader);
+	reftable_buf_release(&buf);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 07/10] t/unit-tests: convert reftable readwrite test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (5 preceding siblings ...)
  2025-04-29 17:52 ` [PATCH v2 06/10] t/unit-tests: convert reftable reader " Seyi Kuforiji
@ 2025-04-29 17:52 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:53 ` [PATCH v2 08/10] t/unit-tests: convert reftable record " Seyi Kuforiji
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:52 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable readwrite test file to use clar by using clar assertions
where necessary.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                            |   2 +-
 t/meson.build                       |   2 +-
 t/unit-tests/t-reftable-readwrite.c | 985 ----------------------------
 t/unit-tests/u-reftable-readwrite.c | 870 ++++++++++++++++++++++++
 4 files changed, 872 insertions(+), 987 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-readwrite.c
 create mode 100644 t/unit-tests/u-reftable-readwrite.c

diff --git a/Makefile b/Makefile
index d3e8677653..88b6851b37 100644
--- a/Makefile
+++ b/Makefile
@@ -1367,6 +1367,7 @@ CLAR_TEST_SUITES += u-reftable-block
 CLAR_TEST_SUITES += u-reftable-merged
 CLAR_TEST_SUITES += u-reftable-pq
 CLAR_TEST_SUITES += u-reftable-reader
+CLAR_TEST_SUITES += u-reftable-readwrite
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1379,7 +1380,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-readwrite
 UNIT_TEST_PROGRAMS += t-reftable-record
 UNIT_TEST_PROGRAMS += t-reftable-stack
 UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
diff --git a/t/meson.build b/t/meson.build
index 6a22bd2790..7722d177e2 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -13,6 +13,7 @@ clar_test_suites = [
   'unit-tests/u-reftable-merged.c',
   'unit-tests/u-reftable-pq.c',
   'unit-tests/u-reftable-reader.c',
+  'unit-tests/u-reftable-readwrite.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -59,7 +60,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-readwrite.c',
   'unit-tests/t-reftable-record.c',
   'unit-tests/t-reftable-stack.c',
 ]
diff --git a/t/unit-tests/t-reftable-readwrite.c b/t/unit-tests/t-reftable-readwrite.c
deleted file mode 100644
index c9626831da..0000000000
--- a/t/unit-tests/t-reftable-readwrite.c
+++ /dev/null
@@ -1,985 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#define DISABLE_SIGN_COMPARE_WARNINGS
-
-#include "test-lib.h"
-#include "lib-reftable.h"
-#include "reftable/basics.h"
-#include "reftable/blocksource.h"
-#include "reftable/reader.h"
-#include "reftable/reftable-error.h"
-#include "reftable/reftable-writer.h"
-#include "strbuf.h"
-
-static const int update_index = 5;
-
-static void t_buffer(void)
-{
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_block_source source = { 0 };
-	struct reftable_block out = { 0 };
-	int n;
-	uint8_t in[] = "hello";
-	check(!reftable_buf_add(&buf, in, sizeof(in)));
-	block_source_from_buf(&source, &buf);
-	check_int(block_source_size(&source), ==, 6);
-	n = block_source_read_block(&source, &out, 0, sizeof(in));
-	check_int(n, ==, sizeof(in));
-	check(!memcmp(in, out.data, n));
-	reftable_block_done(&out);
-
-	n = block_source_read_block(&source, &out, 1, 2);
-	check_int(n, ==, 2);
-	check(!memcmp(out.data, "el", 2));
-
-	reftable_block_done(&out);
-	block_source_close(&source);
-	reftable_buf_release(&buf);
-}
-
-static void write_table(char ***names, struct reftable_buf *buf, int N,
-			int block_size, enum reftable_hash hash_id)
-{
-	struct reftable_write_options opts = {
-		.block_size = block_size,
-		.hash_id = hash_id,
-	};
-	struct reftable_ref_record *refs;
-	struct reftable_log_record *logs;
-	int i;
-
-	REFTABLE_CALLOC_ARRAY(*names, N + 1);
-	check(*names != NULL);
-	REFTABLE_CALLOC_ARRAY(refs, N);
-	check(refs != NULL);
-	REFTABLE_CALLOC_ARRAY(logs, N);
-	check(logs != NULL);
-
-	for (i = 0; i < N; i++) {
-		refs[i].refname = (*names)[i] = xstrfmt("refs/heads/branch%02d", i);
-		refs[i].update_index = update_index;
-		refs[i].value_type = REFTABLE_REF_VAL1;
-		t_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);
-	}
-
-	for (i = 0; i < N; i++) {
-		logs[i].refname = (*names)[i];
-		logs[i].update_index = update_index;
-		logs[i].value_type = REFTABLE_LOG_UPDATE;
-		t_reftable_set_hash(logs[i].value.update.new_hash, i,
-				    REFTABLE_HASH_SHA1);
-		logs[i].value.update.message = (char *) "message";
-	}
-
-	t_reftable_write_to_buf(buf, refs, N, logs, N, &opts);
-
-	reftable_free(refs);
-	reftable_free(logs);
-}
-
-static void t_log_buffer_size(void)
-{
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_write_options opts = {
-		.block_size = 4096,
-	};
-	int err;
-	int i;
-	struct reftable_log_record
-		log = { .refname = (char *) "refs/heads/master",
-			.update_index = update_index,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = { .update = {
-					   .name = (char *) "Han-Wen Nienhuys",
-					   .email = (char *) "hanwen@google.com",
-					   .tz_offset = 100,
-					   .time = 0x5e430672,
-					   .message = (char *) "commit: 9\n",
-				   } } };
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-
-	/* This tests buffer extension for log compression. Must use a random
-	   hash, to ensure that the compressed part is larger than the original.
-	*/
-	for (i = 0; i < REFTABLE_HASH_SIZE_SHA1; i++) {
-		log.value.update.old_hash[i] = (uint8_t)(git_rand(0) % 256);
-		log.value.update.new_hash[i] = (uint8_t)(git_rand(0) % 256);
-	}
-	reftable_writer_set_limits(w, update_index, update_index);
-	err = reftable_writer_add_log(w, &log);
-	check(!err);
-	err = reftable_writer_close(w);
-	check(!err);
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_log_overflow(void)
-{
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	char msg[256] = { 0 };
-	struct reftable_write_options opts = {
-		.block_size = ARRAY_SIZE(msg),
-	};
-	int err;
-	struct reftable_log_record log = {
-		.refname = (char *) "refs/heads/master",
-		.update_index = update_index,
-		.value_type = REFTABLE_LOG_UPDATE,
-		.value = {
-			.update = {
-				.old_hash = { 1 },
-				.new_hash = { 2 },
-				.name = (char *) "Han-Wen Nienhuys",
-				.email = (char *) "hanwen@google.com",
-				.tz_offset = 100,
-				.time = 0x5e430672,
-				.message = msg,
-			},
-		},
-	};
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-
-	memset(msg, 'x', sizeof(msg) - 1);
-	reftable_writer_set_limits(w, update_index, update_index);
-	err = reftable_writer_add_log(w, &log);
-	check_int(err, ==, REFTABLE_ENTRY_TOO_BIG_ERROR);
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_log_write_limits(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_log_record log = {
-		.refname = (char *)"refs/head/master",
-		.update_index = 0,
-		.value_type = REFTABLE_LOG_UPDATE,
-		.value = {
-			.update = {
-				.old_hash = { 1 },
-				.new_hash = { 2 },
-				.name = (char *)"Han-Wen Nienhuys",
-				.email = (char *)"hanwen@google.com",
-				.tz_offset = 100,
-				.time = 0x5e430672,
-			},
-		},
-	};
-	int err;
-
-	reftable_writer_set_limits(w, 1, 1);
-
-	/* write with update_index (0) below set limits (1, 1) */
-	err = reftable_writer_add_log(w, &log);
-	check_int(err, ==, 0);
-
-	/* write with update_index (1) in the set limits (1, 1) */
-	log.update_index = 1;
-	err = reftable_writer_add_log(w, &log);
-	check_int(err, ==, 0);
-
-	/* write with update_index (3) above set limits (1, 1) */
-	log.update_index = 3;
-	err = reftable_writer_add_log(w, &log);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_log_write_read(void)
-{
-	struct reftable_write_options opts = {
-		.block_size = 256,
-	};
-	struct reftable_ref_record ref = { 0 };
-	struct reftable_log_record log = { 0 };
-	struct reftable_iterator it = { 0 };
-	struct reftable_reader *reader;
-	struct reftable_block_source source = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	const struct reftable_stats *stats = NULL;
-	int N = 2, err, i, n;
-	char **names;
-
-	names = reftable_calloc(N + 1, sizeof(*names));
-	check(names != NULL);
-
-	reftable_writer_set_limits(w, 0, N);
-
-	for (i = 0; i < N; i++) {
-		char name[256];
-		struct reftable_ref_record ref = { 0 };
-		snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
-		names[i] = xstrdup(name);
-		ref.refname = name;
-		ref.update_index = i;
-
-		err = reftable_writer_add_ref(w, &ref);
-		check(!err);
-	}
-
-	for (i = 0; i < N; i++) {
-		struct reftable_log_record log = { 0 };
-
-		log.refname = names[i];
-		log.update_index = i;
-		log.value_type = REFTABLE_LOG_UPDATE;
-		t_reftable_set_hash(log.value.update.old_hash, i,
-				    REFTABLE_HASH_SHA1);
-		t_reftable_set_hash(log.value.update.new_hash, i + 1,
-				    REFTABLE_HASH_SHA1);
-
-		err = reftable_writer_add_log(w, &log);
-		check(!err);
-	}
-
-	n = reftable_writer_close(w);
-	check_int(n, ==, 0);
-
-	stats = reftable_writer_stats(w);
-	check_int(stats->log_stats.blocks, >, 0);
-	reftable_writer_free(w);
-	w = NULL;
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&reader, &source, "file.log");
-	check(!err);
-
-	err = reftable_reader_init_ref_iterator(reader, &it);
-	check(!err);
-
-	err = reftable_iterator_seek_ref(&it, names[N - 1]);
-	check(!err);
-
-	err = reftable_iterator_next_ref(&it, &ref);
-	check(!err);
-
-	/* end of iteration. */
-	err = reftable_iterator_next_ref(&it, &ref);
-	check_int(err, >, 0);
-
-	reftable_iterator_destroy(&it);
-	reftable_ref_record_release(&ref);
-
-	err = reftable_reader_init_log_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_log(&it, "");
-	check(!err);
-
-	for (i = 0; ; i++) {
-		int err = reftable_iterator_next_log(&it, &log);
-		if (err > 0)
-			break;
-		check(!err);
-		check_str(names[i], log.refname);
-		check_int(i, ==, log.update_index);
-		reftable_log_record_release(&log);
-	}
-
-	check_int(i, ==, N);
-	reftable_iterator_destroy(&it);
-
-	/* cleanup. */
-	reftable_buf_release(&buf);
-	free_names(names);
-	reftable_reader_decref(reader);
-}
-
-static void t_log_zlib_corruption(void)
-{
-	struct reftable_write_options opts = {
-		.block_size = 256,
-	};
-	struct reftable_iterator it = { 0 };
-	struct reftable_reader *reader;
-	struct reftable_block_source source = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	const struct reftable_stats *stats = NULL;
-	char message[100] = { 0 };
-	int err, i, n;
-	struct reftable_log_record log = {
-		.refname = (char *) "refname",
-		.value_type = REFTABLE_LOG_UPDATE,
-		.value = {
-			.update = {
-				.new_hash = { 1 },
-				.old_hash = { 2 },
-				.name = (char *) "My Name",
-				.email = (char *) "myname@invalid",
-				.message = message,
-			},
-		},
-	};
-
-	for (i = 0; i < sizeof(message) - 1; i++)
-		message[i] = (uint8_t)(git_rand(0) % 64 + ' ');
-
-	reftable_writer_set_limits(w, 1, 1);
-
-	err = reftable_writer_add_log(w, &log);
-	check(!err);
-
-	n = reftable_writer_close(w);
-	check_int(n, ==, 0);
-
-	stats = reftable_writer_stats(w);
-	check_int(stats->log_stats.blocks, >, 0);
-	reftable_writer_free(w);
-	w = NULL;
-
-	/* corrupt the data. */
-	buf.buf[50] ^= 0x99;
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&reader, &source, "file.log");
-	check(!err);
-
-	err = reftable_reader_init_log_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_log(&it, "refname");
-	check_int(err, ==, REFTABLE_ZLIB_ERROR);
-
-	reftable_iterator_destroy(&it);
-
-	/* cleanup. */
-	reftable_reader_decref(reader);
-	reftable_buf_release(&buf);
-}
-
-static void t_table_read_write_sequential(void)
-{
-	char **names;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	int N = 50;
-	struct reftable_iterator it = { 0 };
-	struct reftable_block_source source = { 0 };
-	struct reftable_reader *reader;
-	int err = 0;
-	int j = 0;
-
-	write_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&reader, &source, "file.ref");
-	check(!err);
-
-	err = reftable_reader_init_ref_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, "");
-	check(!err);
-
-	for (j = 0; ; j++) {
-		struct reftable_ref_record ref = { 0 };
-		int r = reftable_iterator_next_ref(&it, &ref);
-		check_int(r, >=, 0);
-		if (r > 0)
-			break;
-		check_str(names[j], ref.refname);
-		check_int(update_index, ==, ref.update_index);
-		reftable_ref_record_release(&ref);
-	}
-	check_int(j, ==, N);
-
-	reftable_iterator_destroy(&it);
-	reftable_reader_decref(reader);
-	reftable_buf_release(&buf);
-	free_names(names);
-}
-
-static void t_table_write_small_table(void)
-{
-	char **names;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	int N = 1;
-	write_table(&names, &buf, N, 4096, REFTABLE_HASH_SHA1);
-	check_int(buf.len, <, 200);
-	reftable_buf_release(&buf);
-	free_names(names);
-}
-
-static void t_table_read_api(void)
-{
-	char **names;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	int N = 50;
-	struct reftable_reader *reader;
-	struct reftable_block_source source = { 0 };
-	int err;
-	struct reftable_log_record log = { 0 };
-	struct reftable_iterator it = { 0 };
-
-	write_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&reader, &source, "file.ref");
-	check(!err);
-
-	err = reftable_reader_init_ref_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, names[0]);
-	check(!err);
-
-	err = reftable_iterator_next_log(&it, &log);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	reftable_buf_release(&buf);
-	free_names(names);
-	reftable_iterator_destroy(&it);
-	reftable_reader_decref(reader);
-	reftable_buf_release(&buf);
-}
-
-static void t_table_read_write_seek(int index, enum reftable_hash hash_id)
-{
-	char **names;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	int N = 50;
-	struct reftable_reader *reader;
-	struct reftable_block_source source = { 0 };
-	int err;
-	int i = 0;
-
-	struct reftable_iterator it = { 0 };
-	struct reftable_buf pastLast = REFTABLE_BUF_INIT;
-	struct reftable_ref_record ref = { 0 };
-
-	write_table(&names, &buf, N, 256, hash_id);
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&reader, &source, "file.ref");
-	check(!err);
-	check_int(hash_id, ==, reftable_reader_hash_id(reader));
-
-	if (!index) {
-		reader->ref_offsets.index_offset = 0;
-	} else {
-		check_int(reader->ref_offsets.index_offset, >, 0);
-	}
-
-	for (i = 1; i < N; i++) {
-		err = reftable_reader_init_ref_iterator(reader, &it);
-		check(!err);
-		err = reftable_iterator_seek_ref(&it, names[i]);
-		check(!err);
-		err = reftable_iterator_next_ref(&it, &ref);
-		check(!err);
-		check_str(names[i], ref.refname);
-		check_int(REFTABLE_REF_VAL1, ==, ref.value_type);
-		check_int(i, ==, ref.value.val1[0]);
-
-		reftable_ref_record_release(&ref);
-		reftable_iterator_destroy(&it);
-	}
-
-	check(!reftable_buf_addstr(&pastLast, names[N - 1]));
-	check(!reftable_buf_addstr(&pastLast, "/"));
-
-	err = reftable_reader_init_ref_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, pastLast.buf);
-	if (err == 0) {
-		struct reftable_ref_record ref = { 0 };
-		int err = reftable_iterator_next_ref(&it, &ref);
-		check_int(err, >, 0);
-	} else {
-		check_int(err, >, 0);
-	}
-
-	reftable_buf_release(&pastLast);
-	reftable_iterator_destroy(&it);
-
-	reftable_buf_release(&buf);
-	free_names(names);
-	reftable_reader_decref(reader);
-}
-
-static void t_table_read_write_seek_linear(void)
-{
-	t_table_read_write_seek(0, REFTABLE_HASH_SHA1);
-}
-
-static void t_table_read_write_seek_linear_sha256(void)
-{
-	t_table_read_write_seek(0, REFTABLE_HASH_SHA256);
-}
-
-static void t_table_read_write_seek_index(void)
-{
-	t_table_read_write_seek(1, REFTABLE_HASH_SHA1);
-}
-
-static void t_table_refs_for(int indexed)
-{
-	char **want_names;
-	int want_names_len = 0;
-	uint8_t want_hash[REFTABLE_HASH_SIZE_SHA1];
-
-	struct reftable_write_options opts = {
-		.block_size = 256,
-	};
-	struct reftable_ref_record ref = { 0 };
-	struct reftable_reader *reader;
-	struct reftable_block_source source = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_iterator it = { 0 };
-	int N = 50, n, j, err, i;
-
-	want_names = reftable_calloc(N + 1, sizeof(*want_names));
-	check(want_names != NULL);
-
-	t_reftable_set_hash(want_hash, 4, REFTABLE_HASH_SHA1);
-
-	for (i = 0; i < N; i++) {
-		uint8_t hash[REFTABLE_HASH_SIZE_SHA1];
-		char fill[51] = { 0 };
-		char name[100];
-		struct reftable_ref_record ref = { 0 };
-
-		memset(hash, i, sizeof(hash));
-		memset(fill, 'x', 50);
-		/* Put the variable part in the start */
-		snprintf(name, sizeof(name), "br%02d%s", i, fill);
-		name[40] = 0;
-		ref.refname = name;
-
-		ref.value_type = REFTABLE_REF_VAL2;
-		t_reftable_set_hash(ref.value.val2.value, i / 4,
-				    REFTABLE_HASH_SHA1);
-		t_reftable_set_hash(ref.value.val2.target_value, 3 + i / 4,
-				    REFTABLE_HASH_SHA1);
-
-		/* 80 bytes / entry, so 3 entries per block. Yields 17
-		 */
-		/* blocks. */
-		n = reftable_writer_add_ref(w, &ref);
-		check_int(n, ==, 0);
-
-		if (!memcmp(ref.value.val2.value, want_hash, REFTABLE_HASH_SIZE_SHA1) ||
-		    !memcmp(ref.value.val2.target_value, want_hash, REFTABLE_HASH_SIZE_SHA1))
-			want_names[want_names_len++] = xstrdup(name);
-	}
-
-	n = reftable_writer_close(w);
-	check_int(n, ==, 0);
-
-	reftable_writer_free(w);
-	w = NULL;
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&reader, &source, "file.ref");
-	check(!err);
-	if (!indexed)
-		reader->obj_offsets.is_present = 0;
-
-	err = reftable_reader_init_ref_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, "");
-	check(!err);
-	reftable_iterator_destroy(&it);
-
-	err = reftable_reader_refs_for(reader, &it, want_hash);
-	check(!err);
-
-	for (j = 0; ; j++) {
-		int err = reftable_iterator_next_ref(&it, &ref);
-		check_int(err, >=, 0);
-		if (err > 0)
-			break;
-		check_int(j, <, want_names_len);
-		check_str(ref.refname, want_names[j]);
-		reftable_ref_record_release(&ref);
-	}
-	check_int(j, ==, want_names_len);
-
-	reftable_buf_release(&buf);
-	free_names(want_names);
-	reftable_iterator_destroy(&it);
-	reftable_reader_decref(reader);
-}
-
-static void t_table_refs_for_no_index(void)
-{
-	t_table_refs_for(0);
-}
-
-static void t_table_refs_for_obj_index(void)
-{
-	t_table_refs_for(1);
-}
-
-static void t_write_empty_table(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_block_source source = { 0 };
-	struct reftable_reader *rd = NULL;
-	struct reftable_ref_record rec = { 0 };
-	struct reftable_iterator it = { 0 };
-	int err;
-
-	reftable_writer_set_limits(w, 1, 1);
-
-	err = reftable_writer_close(w);
-	check_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR);
-	reftable_writer_free(w);
-
-	check_uint(buf.len, ==, header_size(1) + footer_size(1));
-
-	block_source_from_buf(&source, &buf);
-
-	err = reftable_reader_new(&rd, &source, "filename");
-	check(!err);
-
-	err = reftable_reader_init_ref_iterator(rd, &it);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, "");
-	check(!err);
-
-	err = reftable_iterator_next_ref(&it, &rec);
-	check_int(err, >, 0);
-
-	reftable_iterator_destroy(&it);
-	reftable_reader_decref(rd);
-	reftable_buf_release(&buf);
-}
-
-static void t_write_object_id_min_length(void)
-{
-	struct reftable_write_options opts = {
-		.block_size = 75,
-	};
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_ref_record ref = {
-		.update_index = 1,
-		.value_type = REFTABLE_REF_VAL1,
-		.value.val1 = {42},
-	};
-	int err;
-	int i;
-
-	reftable_writer_set_limits(w, 1, 1);
-
-	/* Write the same hash in many refs. If there is only 1 hash, the
-	 * disambiguating prefix is length 0 */
-	for (i = 0; i < 256; i++) {
-		char name[256];
-		snprintf(name, sizeof(name), "ref%05d", i);
-		ref.refname = name;
-		err = reftable_writer_add_ref(w, &ref);
-		check(!err);
-	}
-
-	err = reftable_writer_close(w);
-	check(!err);
-	check_int(reftable_writer_stats(w)->object_id_len, ==, 2);
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_write_object_id_length(void)
-{
-	struct reftable_write_options opts = {
-		.block_size = 75,
-	};
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_ref_record ref = {
-		.update_index = 1,
-		.value_type = REFTABLE_REF_VAL1,
-		.value.val1 = {42},
-	};
-	int err;
-	int i;
-
-	reftable_writer_set_limits(w, 1, 1);
-
-	/* Write the same hash in many refs. If there is only 1 hash, the
-	 * disambiguating prefix is length 0 */
-	for (i = 0; i < 256; i++) {
-		char name[256];
-		snprintf(name, sizeof(name), "ref%05d", i);
-		ref.refname = name;
-		ref.value.val1[15] = i;
-		err = reftable_writer_add_ref(w, &ref);
-		check(!err);
-	}
-
-	err = reftable_writer_close(w);
-	check(!err);
-	check_int(reftable_writer_stats(w)->object_id_len, ==, 16);
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_write_empty_key(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_ref_record ref = {
-		.refname = (char *) "",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_DELETION,
-	};
-	int err;
-
-	reftable_writer_set_limits(w, 1, 1);
-	err = reftable_writer_add_ref(w, &ref);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	err = reftable_writer_close(w);
-	check_int(err, ==, REFTABLE_EMPTY_TABLE_ERROR);
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_write_key_order(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_writer *w = t_reftable_strbuf_writer(&buf, &opts);
-	struct reftable_ref_record refs[2] = {
-		{
-			.refname = (char *) "b",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_SYMREF,
-			.value = {
-				.symref = (char *) "target",
-			},
-		}, {
-			.refname = (char *) "a",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_SYMREF,
-			.value = {
-				.symref = (char *) "target",
-			},
-		}
-	};
-	int err;
-
-	reftable_writer_set_limits(w, 1, 1);
-	err = reftable_writer_add_ref(w, &refs[0]);
-	check(!err);
-	err = reftable_writer_add_ref(w, &refs[1]);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	refs[0].update_index = 2;
-	err = reftable_writer_add_ref(w, &refs[0]);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	reftable_writer_close(w);
-	reftable_writer_free(w);
-	reftable_buf_release(&buf);
-}
-
-static void t_write_multiple_indices(void)
-{
-	struct reftable_write_options opts = {
-		.block_size = 100,
-	};
-	struct reftable_buf writer_buf = REFTABLE_BUF_INIT;
-	struct reftable_block_source source = { 0 };
-	struct reftable_iterator it = { 0 };
-	const struct reftable_stats *stats;
-	struct reftable_writer *writer;
-	struct reftable_reader *reader;
-	char buf[128];
-	int err, i;
-
-	writer = t_reftable_strbuf_writer(&writer_buf, &opts);
-	reftable_writer_set_limits(writer, 1, 1);
-	for (i = 0; i < 100; i++) {
-		struct reftable_ref_record ref = {
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = {i},
-		};
-
-		snprintf(buf, sizeof(buf), "refs/heads/%04d", i);
-		ref.refname = buf;
-
-		err = reftable_writer_add_ref(writer, &ref);
-		check(!err);
-	}
-
-	for (i = 0; i < 100; i++) {
-		struct reftable_log_record log = {
-			.update_index = 1,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value.update = {
-				.old_hash = { i },
-				.new_hash = { i },
-			},
-		};
-
-		snprintf(buf, sizeof(buf), "refs/heads/%04d", i);
-		log.refname = buf;
-
-		err = reftable_writer_add_log(writer, &log);
-		check(!err);
-	}
-
-	reftable_writer_close(writer);
-
-	/*
-	 * The written data should be sufficiently large to result in indices
-	 * for each of the block types.
-	 */
-	stats = reftable_writer_stats(writer);
-	check_int(stats->ref_stats.index_offset, >, 0);
-	check_int(stats->obj_stats.index_offset, >, 0);
-	check_int(stats->log_stats.index_offset, >, 0);
-
-	block_source_from_buf(&source, &writer_buf);
-	err = reftable_reader_new(&reader, &source, "filename");
-	check(!err);
-
-	/*
-	 * Seeking the log uses the log index now. In case there is any
-	 * confusion regarding indices we would notice here.
-	 */
-	err = reftable_reader_init_log_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_log(&it, "");
-	check(!err);
-
-	reftable_iterator_destroy(&it);
-	reftable_writer_free(writer);
-	reftable_reader_decref(reader);
-	reftable_buf_release(&writer_buf);
-}
-
-static void t_write_multi_level_index(void)
-{
-	struct reftable_write_options opts = {
-		.block_size = 100,
-	};
-	struct reftable_buf writer_buf = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
-	struct reftable_block_source source = { 0 };
-	struct reftable_iterator it = { 0 };
-	const struct reftable_stats *stats;
-	struct reftable_writer *writer;
-	struct reftable_reader *reader;
-	int err;
-
-	writer = t_reftable_strbuf_writer(&writer_buf, &opts);
-	reftable_writer_set_limits(writer, 1, 1);
-	for (size_t i = 0; i < 200; i++) {
-		struct reftable_ref_record ref = {
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = {i},
-		};
-		char buf[128];
-
-		snprintf(buf, sizeof(buf), "refs/heads/%03" PRIuMAX, (uintmax_t)i);
-		ref.refname = buf;
-
-		err = reftable_writer_add_ref(writer, &ref);
-		check(!err);
-	}
-	reftable_writer_close(writer);
-
-	/*
-	 * The written refs should be sufficiently large to result in a
-	 * multi-level index.
-	 */
-	stats = reftable_writer_stats(writer);
-	check_int(stats->ref_stats.max_index_level, ==, 2);
-
-	block_source_from_buf(&source, &writer_buf);
-	err = reftable_reader_new(&reader, &source, "filename");
-	check(!err);
-
-	/*
-	 * Seeking the last ref should work as expected.
-	 */
-	err = reftable_reader_init_ref_iterator(reader, &it);
-	check(!err);
-	err = reftable_iterator_seek_ref(&it, "refs/heads/199");
-	check(!err);
-
-	reftable_iterator_destroy(&it);
-	reftable_writer_free(writer);
-	reftable_reader_decref(reader);
-	reftable_buf_release(&writer_buf);
-	reftable_buf_release(&buf);
-}
-
-static void t_corrupt_table_empty(void)
-{
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_block_source source = { 0 };
-	struct reftable_reader *reader;
-	int err;
-
-	block_source_from_buf(&source, &buf);
-	err = reftable_reader_new(&reader, &source, "file.log");
-	check_int(err, ==, REFTABLE_FORMAT_ERROR);
-}
-
-static void t_corrupt_table(void)
-{
-	uint8_t zeros[1024] = { 0 };
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	struct reftable_block_source source = { 0 };
-	struct reftable_reader *reader;
-	int err;
-	check(!reftable_buf_add(&buf, zeros, sizeof(zeros)));
-
-	block_source_from_buf(&source, &buf);
-	err = reftable_reader_new(&reader, &source, "file.log");
-	check_int(err, ==, REFTABLE_FORMAT_ERROR);
-
-	reftable_buf_release(&buf);
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_buffer(), "strbuf works as blocksource");
-	TEST(t_corrupt_table(), "read-write on corrupted table");
-	TEST(t_corrupt_table_empty(), "read-write on an empty table");
-	TEST(t_log_buffer_size(), "buffer extension for log compression");
-	TEST(t_log_overflow(), "log overflow returns expected error");
-	TEST(t_log_write_limits(), "writer limits for writing log records");
-	TEST(t_log_write_read(), "read-write on log records");
-	TEST(t_log_zlib_corruption(), "reading corrupted log record returns expected error");
-	TEST(t_table_read_api(), "read on a table");
-	TEST(t_table_read_write_seek_index(), "read-write on a table with index");
-	TEST(t_table_read_write_seek_linear(), "read-write on a table without index (SHA1)");
-	TEST(t_table_read_write_seek_linear_sha256(), "read-write on a table without index (SHA256)");
-	TEST(t_table_read_write_sequential(), "sequential read-write on a table");
-	TEST(t_table_refs_for_no_index(), "refs-only table with no index");
-	TEST(t_table_refs_for_obj_index(), "refs-only table with index");
-	TEST(t_table_write_small_table(), "write_table works");
-	TEST(t_write_empty_key(), "write on refs with empty keys");
-	TEST(t_write_empty_table(), "read-write on empty tables");
-	TEST(t_write_key_order(), "refs must be written in increasing order");
-	TEST(t_write_multi_level_index(), "table with multi-level index");
-	TEST(t_write_multiple_indices(), "table with indices for multiple block types");
-	TEST(t_write_object_id_length(), "prefix compression on writing refs");
-	TEST(t_write_object_id_min_length(), "prefix compression on writing refs");
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c
new file mode 100644
index 0000000000..3d6bdcfceb
--- /dev/null
+++ b/t/unit-tests/u-reftable-readwrite.c
@@ -0,0 +1,870 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#define DISABLE_SIGN_COMPARE_WARNINGS
+
+#include "unit-test.h"
+#include "lib-reftable.h"
+#include "reftable/basics.h"
+#include "reftable/blocksource.h"
+#include "reftable/reader.h"
+#include "reftable/reftable-error.h"
+#include "reftable/reftable-writer.h"
+#include "strbuf.h"
+
+static const int update_index = 5;
+
+void test_reftable_readwrite__buffer(void)
+{
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_block_source source = { 0 };
+	struct reftable_block out = { 0 };
+	int n;
+	uint8_t in[] = "hello";
+	cl_assert(reftable_buf_add(&buf, in, sizeof(in)) == 0);
+	block_source_from_buf(&source, &buf);
+	cl_assert_equal_i(block_source_size(&source), 6);
+	n = block_source_read_block(&source, &out, 0, sizeof(in));
+	cl_assert_equal_i(n, sizeof(in));
+	cl_assert(memcmp(in, out.data, n) == 0);
+	reftable_block_done(&out);
+
+	cl_assert_equal_i(block_source_read_block(&source, &out, 1, 2), 2);
+	cl_assert(memcmp(out.data, "el", 2) == 0);
+
+	reftable_block_done(&out);
+	block_source_close(&source);
+	reftable_buf_release(&buf);
+}
+
+static void write_table(char ***names, struct reftable_buf *buf, int N,
+			int block_size, enum reftable_hash hash_id)
+{
+	struct reftable_write_options opts = {
+		.block_size = block_size,
+		.hash_id = hash_id,
+	};
+	struct reftable_ref_record *refs;
+	struct reftable_log_record *logs;
+	int i;
+
+	REFTABLE_CALLOC_ARRAY(*names, N + 1);
+	cl_assert(*names != NULL);
+	REFTABLE_CALLOC_ARRAY(refs, N);
+	cl_assert(refs != NULL);
+	REFTABLE_CALLOC_ARRAY(logs, N);
+	cl_assert(logs != NULL);
+
+	for (i = 0; i < N; i++) {
+		refs[i].refname = (*names)[i] = xstrfmt("refs/heads/branch%02d", i);
+		refs[i].update_index = update_index;
+		refs[i].value_type = REFTABLE_REF_VAL1;
+		cl_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);
+	}
+
+	for (i = 0; i < N; i++) {
+		logs[i].refname = (*names)[i];
+		logs[i].update_index = update_index;
+		logs[i].value_type = REFTABLE_LOG_UPDATE;
+		cl_reftable_set_hash(logs[i].value.update.new_hash, i,
+				    REFTABLE_HASH_SHA1);
+		logs[i].value.update.message = (char *) "message";
+	}
+
+	cl_reftable_write_to_buf(buf, refs, N, logs, N, &opts);
+
+	reftable_free(refs);
+	reftable_free(logs);
+}
+
+void test_reftable_readwrite__log_buffer_size(void)
+{
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_write_options opts = {
+		.block_size = 4096,
+	};
+	int i;
+	struct reftable_log_record
+		log = { .refname = (char *) "refs/heads/master",
+			.update_index = update_index,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value = { .update = {
+					   .name = (char *) "Han-Wen Nienhuys",
+					   .email = (char *) "hanwen@google.com",
+					   .tz_offset = 100,
+					   .time = 0x5e430672,
+					   .message = (char *) "commit: 9\n",
+				   } } };
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+
+	/* This tests buffer extension for log compression. Must use a random
+	   hash, to ensure that the compressed part is larger than the original.
+	*/
+	for (i = 0; i < REFTABLE_HASH_SIZE_SHA1; i++) {
+		log.value.update.old_hash[i] = (uint8_t)(git_rand(0) % 256);
+		log.value.update.new_hash[i] = (uint8_t)(git_rand(0) % 256);
+	}
+	reftable_writer_set_limits(w, update_index, update_index);
+	cl_assert(reftable_writer_add_log(w, &log) == 0);
+	cl_assert(reftable_writer_close(w) == 0);
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__log_overflow(void)
+{
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	char msg[256] = { 0 };
+	struct reftable_write_options opts = {
+		.block_size = ARRAY_SIZE(msg),
+	};
+	struct reftable_log_record log = {
+		.refname = (char *) "refs/heads/master",
+		.update_index = update_index,
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.old_hash = { 1 },
+				.new_hash = { 2 },
+				.name = (char *) "Han-Wen Nienhuys",
+				.email = (char *) "hanwen@google.com",
+				.tz_offset = 100,
+				.time = 0x5e430672,
+				.message = msg,
+			},
+		},
+	};
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+
+	memset(msg, 'x', sizeof(msg) - 1);
+	reftable_writer_set_limits(w, update_index, update_index);
+	cl_assert_equal_i(reftable_writer_add_log(w, &log), REFTABLE_ENTRY_TOO_BIG_ERROR);
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__log_write_limits(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_log_record log = {
+		.refname = (char *)"refs/head/master",
+		.update_index = 0,
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.old_hash = { 1 },
+				.new_hash = { 2 },
+				.name = (char *)"Han-Wen Nienhuys",
+				.email = (char *)"hanwen@google.com",
+				.tz_offset = 100,
+				.time = 0x5e430672,
+			},
+		},
+	};
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	/* write with update_index (0) below set limits (1, 1) */
+	cl_assert_equal_i(reftable_writer_add_log(w, &log), 0);
+
+	/* write with update_index (1) in the set limits (1, 1) */
+	log.update_index = 1;
+	cl_assert_equal_i(reftable_writer_add_log(w, &log), 0);
+
+	/* write with update_index (3) above set limits (1, 1) */
+	log.update_index = 3;
+	cl_assert_equal_i(reftable_writer_add_log(w, &log), REFTABLE_API_ERROR);
+
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__log_write_read(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	struct reftable_ref_record ref = { 0 };
+	struct reftable_log_record log = { 0 };
+	struct reftable_iterator it = { 0 };
+	struct reftable_reader *reader;
+	struct reftable_block_source source = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	const struct reftable_stats *stats = NULL;
+	int N = 2, i;
+	char **names;
+
+	names = reftable_calloc(N + 1, sizeof(*names));
+	cl_assert(names != NULL);
+
+	reftable_writer_set_limits(w, 0, N);
+
+	for (i = 0; i < N; i++) {
+		char name[256];
+		struct reftable_ref_record ref = { 0 };
+		snprintf(name, sizeof(name), "b%02d%0*d", i, 130, 7);
+		names[i] = xstrdup(name);
+		ref.refname = name;
+		ref.update_index = i;
+
+		cl_assert(reftable_writer_add_ref(w, &ref) == 0);
+	}
+
+	for (i = 0; i < N; i++) {
+		struct reftable_log_record log = { 0 };
+
+		log.refname = names[i];
+		log.update_index = i;
+		log.value_type = REFTABLE_LOG_UPDATE;
+		cl_reftable_set_hash(log.value.update.old_hash, i,
+				    REFTABLE_HASH_SHA1);
+		cl_reftable_set_hash(log.value.update.new_hash, i + 1,
+				    REFTABLE_HASH_SHA1);
+
+		cl_assert(reftable_writer_add_log(w, &log) == 0);
+	}
+
+	cl_assert_equal_i(reftable_writer_close(w), 0);
+
+	stats = reftable_writer_stats(w);
+	cl_assert(stats->log_stats.blocks > 0);
+	reftable_writer_free(w);
+	w = NULL;
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "file.log") == 0);
+	cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_ref(&it, names[N - 1]) == 0);
+	cl_assert(reftable_iterator_next_ref(&it, &ref) == 0);
+
+	/* end of iteration. */
+	cl_assert(reftable_iterator_next_ref(&it, &ref) > 0);
+
+	reftable_iterator_destroy(&it);
+	reftable_ref_record_release(&ref);
+
+	cl_assert(reftable_reader_init_log_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_log(&it, "") == 0);
+
+	for (i = 0; ; i++) {
+		int err = reftable_iterator_next_log(&it, &log);
+		if (err > 0)
+			break;
+		cl_assert(err == 0);
+		cl_assert_equal_s(names[i], log.refname);
+		cl_assert_equal_i(i, log.update_index);
+		reftable_log_record_release(&log);
+	}
+
+	cl_assert_equal_i(i, N);
+	reftable_iterator_destroy(&it);
+
+	/* cleanup. */
+	reftable_buf_release(&buf);
+	free_names(names);
+	reftable_reader_decref(reader);
+}
+
+void test_reftable_readwrite__log_zlib_corruption(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	struct reftable_iterator it = { 0 };
+	struct reftable_reader *reader;
+	struct reftable_block_source source = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	const struct reftable_stats *stats = NULL;
+	char message[100] = { 0 };
+	int i;
+	struct reftable_log_record log = {
+		.refname = (char *) "refname",
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.new_hash = { 1 },
+				.old_hash = { 2 },
+				.name = (char *) "My Name",
+				.email = (char *) "myname@invalid",
+				.message = message,
+			},
+		},
+	};
+
+	for (i = 0; i < sizeof(message) - 1; i++)
+		message[i] = (uint8_t)(git_rand(0) % 64 + ' ');
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	cl_assert(reftable_writer_add_log(w, &log) == 0);
+	cl_assert_equal_i(reftable_writer_close(w), 0);
+
+	stats = reftable_writer_stats(w);
+	cl_assert(stats->log_stats.blocks > 0);
+	reftable_writer_free(w);
+	w = NULL;
+
+	/* corrupt the data. */
+	buf.buf[50] ^= 0x99;
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "file.log") == 0);
+	cl_assert(reftable_reader_init_log_iterator(reader, &it) == 0);
+	cl_assert_equal_i(reftable_iterator_seek_log(&it, "refname"), REFTABLE_ZLIB_ERROR);
+
+	reftable_iterator_destroy(&it);
+
+	/* cleanup. */
+	reftable_reader_decref(reader);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__table_read_write_sequential(void)
+{
+	char **names;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	int N = 50;
+	struct reftable_iterator it = { 0 };
+	struct reftable_block_source source = { 0 };
+	struct reftable_reader *reader;
+	int j = 0;
+
+	write_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "file.ref") == 0);
+	cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+
+	for (j = 0; ; j++) {
+		struct reftable_ref_record ref = { 0 };
+		int r = reftable_iterator_next_ref(&it, &ref);
+		cl_assert(r >= 0);
+		if (r > 0)
+			break;
+		cl_assert_equal_s(names[j], ref.refname);
+		cl_assert_equal_i(update_index, ref.update_index);
+		reftable_ref_record_release(&ref);
+	}
+	cl_assert_equal_i(j, N);
+
+	reftable_iterator_destroy(&it);
+	reftable_reader_decref(reader);
+	reftable_buf_release(&buf);
+	free_names(names);
+}
+
+void test_reftable_readwrite__table_write_small_table(void)
+{
+	char **names;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	int N = 1;
+	write_table(&names, &buf, N, 4096, REFTABLE_HASH_SHA1);
+	cl_assert(buf.len < 200);
+	reftable_buf_release(&buf);
+	free_names(names);
+}
+
+void test_reftable_readwrite__table_read_api(void)
+{
+	char **names;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	int N = 50;
+	struct reftable_reader *reader;
+	struct reftable_block_source source = { 0 };
+	struct reftable_log_record log = { 0 };
+	struct reftable_iterator it = { 0 };
+
+	write_table(&names, &buf, N, 256, REFTABLE_HASH_SHA1);
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "file.ref") == 0);
+	cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_ref(&it, names[0]) == 0);
+
+	cl_assert_equal_i(reftable_iterator_next_log(&it, &log), REFTABLE_API_ERROR);
+
+	reftable_buf_release(&buf);
+	free_names(names);
+	reftable_iterator_destroy(&it);
+	reftable_reader_decref(reader);
+	reftable_buf_release(&buf);
+}
+
+static void t_table_read_write_seek(int index, enum reftable_hash hash_id)
+{
+	char **names;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	int N = 50;
+	struct reftable_reader *reader;
+	struct reftable_block_source source = { 0 };
+	int err;
+	int i = 0;
+
+	struct reftable_iterator it = { 0 };
+	struct reftable_buf pastLast = REFTABLE_BUF_INIT;
+	struct reftable_ref_record ref = { 0 };
+
+	write_table(&names, &buf, N, 256, hash_id);
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "file.ref") == 0);
+	cl_assert_equal_i(hash_id, reftable_reader_hash_id(reader));
+
+	if (!index) {
+		reader->ref_offsets.index_offset = 0;
+	} else {
+		cl_assert(reader->ref_offsets.index_offset > 0);
+	}
+
+	for (i = 1; i < N; i++) {
+		cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+		cl_assert(reftable_iterator_seek_ref(&it, names[i]) == 0);
+		cl_assert(reftable_iterator_next_ref(&it, &ref) == 0);
+		cl_assert_equal_s(names[i], ref.refname);
+		cl_assert_equal_i(REFTABLE_REF_VAL1, ref.value_type);
+		cl_assert_equal_i(i, ref.value.val1[0]);
+
+		reftable_ref_record_release(&ref);
+		reftable_iterator_destroy(&it);
+	}
+
+	cl_assert(reftable_buf_addstr(&pastLast, names[N - 1]) == 0);
+	cl_assert(reftable_buf_addstr(&pastLast, "/") == 0);
+
+	cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+	err = reftable_iterator_seek_ref(&it, pastLast.buf);
+	if (err == 0) {
+		struct reftable_ref_record ref = { 0 };
+		int err = reftable_iterator_next_ref(&it, &ref);
+		cl_assert(err > 0);
+	} else {
+		cl_assert(err > 0);
+	}
+
+	reftable_buf_release(&pastLast);
+	reftable_iterator_destroy(&it);
+
+	reftable_buf_release(&buf);
+	free_names(names);
+	reftable_reader_decref(reader);
+}
+
+void test_reftable_readwrite__table_read_write_seek_linear(void)
+{
+	t_table_read_write_seek(0, REFTABLE_HASH_SHA1);
+}
+
+void test_reftable_readwrite__table_read_write_seek_linear_sha256(void)
+{
+	t_table_read_write_seek(0, REFTABLE_HASH_SHA256);
+}
+
+void test_reftable_readwrite__table_read_write_seek_index(void)
+{
+	t_table_read_write_seek(1, REFTABLE_HASH_SHA1);
+}
+
+static void t_table_refs_for(int indexed)
+{
+	char **want_names;
+	int want_names_len = 0;
+	uint8_t want_hash[REFTABLE_HASH_SIZE_SHA1];
+
+	struct reftable_write_options opts = {
+		.block_size = 256,
+	};
+	struct reftable_ref_record ref = { 0 };
+	struct reftable_reader *reader;
+	struct reftable_block_source source = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_iterator it = { 0 };
+	int N = 50, j, i;
+
+	want_names = reftable_calloc(N + 1, sizeof(*want_names));
+	cl_assert(want_names != NULL);
+
+	cl_reftable_set_hash(want_hash, 4, REFTABLE_HASH_SHA1);
+
+	for (i = 0; i < N; i++) {
+		uint8_t hash[REFTABLE_HASH_SIZE_SHA1];
+		char fill[51] = { 0 };
+		char name[100];
+		struct reftable_ref_record ref = { 0 };
+
+		memset(hash, i, sizeof(hash));
+		memset(fill, 'x', 50);
+		/* Put the variable part in the start */
+		snprintf(name, sizeof(name), "br%02d%s", i, fill);
+		name[40] = 0;
+		ref.refname = name;
+
+		ref.value_type = REFTABLE_REF_VAL2;
+		cl_reftable_set_hash(ref.value.val2.value, i / 4,
+				    REFTABLE_HASH_SHA1);
+		cl_reftable_set_hash(ref.value.val2.target_value, 3 + i / 4,
+				    REFTABLE_HASH_SHA1);
+
+		/* 80 bytes / entry, so 3 entries per block. Yields 17
+		 */
+		/* blocks. */
+		cl_assert_equal_i(reftable_writer_add_ref(w, &ref), 0);
+
+		if (!memcmp(ref.value.val2.value, want_hash, REFTABLE_HASH_SIZE_SHA1) ||
+		    !memcmp(ref.value.val2.target_value, want_hash, REFTABLE_HASH_SIZE_SHA1))
+			want_names[want_names_len++] = xstrdup(name);
+	}
+
+	cl_assert_equal_i(reftable_writer_close(w), 0);
+
+	reftable_writer_free(w);
+	w = NULL;
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&reader, &source, "file.ref") == 0);
+	if (!indexed)
+		reader->obj_offsets.is_present = 0;
+
+	cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+	reftable_iterator_destroy(&it);
+
+	cl_assert(reftable_reader_refs_for(reader, &it, want_hash) == 0);
+
+	for (j = 0; ; j++) {
+		int err = reftable_iterator_next_ref(&it, &ref);
+		cl_assert(err >= 0);
+		if (err > 0)
+			break;
+		cl_assert(j < want_names_len);
+		cl_assert_equal_s(ref.refname, want_names[j]);
+		reftable_ref_record_release(&ref);
+	}
+	cl_assert_equal_i(j, want_names_len);
+
+	reftable_buf_release(&buf);
+	free_names(want_names);
+	reftable_iterator_destroy(&it);
+	reftable_reader_decref(reader);
+}
+
+void test_reftable_readwrite__table_refs_for_no_index(void)
+{
+	t_table_refs_for(0);
+}
+
+void test_reftable_readwrite__table_refs_for_obj_index(void)
+{
+	t_table_refs_for(1);
+}
+
+void test_reftable_readwrite__write_empty_table(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_block_source source = { 0 };
+	struct reftable_reader *rd = NULL;
+	struct reftable_ref_record rec = { 0 };
+	struct reftable_iterator it = { 0 };
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	cl_assert_equal_i(reftable_writer_close(w), REFTABLE_EMPTY_TABLE_ERROR);
+	reftable_writer_free(w);
+
+	cl_assert_equal_i(buf.len, header_size(1) + footer_size(1));
+
+	block_source_from_buf(&source, &buf);
+
+	cl_assert(reftable_reader_new(&rd, &source, "filename") == 0);
+	cl_assert(reftable_reader_init_ref_iterator(rd, &it) == 0);
+	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+	cl_assert(reftable_iterator_next_ref(&it, &rec) > 0);
+
+	reftable_iterator_destroy(&it);
+	reftable_reader_decref(rd);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__write_object_id_min_length(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 75,
+	};
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_ref_record ref = {
+		.update_index = 1,
+		.value_type = REFTABLE_REF_VAL1,
+		.value.val1 = {42},
+	};
+	int i;
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	/* Write the same hash in many refs. If there is only 1 hash, the
+	 * disambiguating prefix is length 0 */
+	for (i = 0; i < 256; i++) {
+		char name[256];
+		snprintf(name, sizeof(name), "ref%05d", i);
+		ref.refname = name;
+		cl_assert(reftable_writer_add_ref(w, &ref) == 0);
+	}
+
+	cl_assert(reftable_writer_close(w) == 0);
+	cl_assert_equal_i(reftable_writer_stats(w)->object_id_len, 2);
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__write_object_id_length(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 75,
+	};
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_ref_record ref = {
+		.update_index = 1,
+		.value_type = REFTABLE_REF_VAL1,
+		.value.val1 = {42},
+	};
+	int i;
+
+	reftable_writer_set_limits(w, 1, 1);
+
+	/* Write the same hash in many refs. If there is only 1 hash, the
+	 * disambiguating prefix is length 0 */
+	for (i = 0; i < 256; i++) {
+		char name[256];
+		snprintf(name, sizeof(name), "ref%05d", i);
+		ref.refname = name;
+		ref.value.val1[15] = i;
+		cl_assert(reftable_writer_add_ref(w, &ref) == 0);
+	}
+
+	cl_assert(reftable_writer_close(w) == 0);
+	cl_assert_equal_i(reftable_writer_stats(w)->object_id_len, 16);
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__write_empty_key(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_ref_record ref = {
+		.refname = (char *) "",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_DELETION,
+	};
+
+	reftable_writer_set_limits(w, 1, 1);
+	cl_assert_equal_i(reftable_writer_add_ref(w, &ref), REFTABLE_API_ERROR);
+	cl_assert_equal_i(reftable_writer_close(w), REFTABLE_EMPTY_TABLE_ERROR);
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__write_key_order(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_writer *w = cl_reftable_strbuf_writer(&buf, &opts);
+	struct reftable_ref_record refs[2] = {
+		{
+			.refname = (char *) "b",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_SYMREF,
+			.value = {
+				.symref = (char *) "target",
+			},
+		}, {
+			.refname = (char *) "a",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_SYMREF,
+			.value = {
+				.symref = (char *) "target",
+			},
+		}
+	};
+
+	reftable_writer_set_limits(w, 1, 1);
+	cl_assert(reftable_writer_add_ref(w, &refs[0]) == 0);
+	cl_assert_equal_i(reftable_writer_add_ref(w, &refs[1]), REFTABLE_API_ERROR);
+
+	refs[0].update_index = 2;
+	cl_assert_equal_i(reftable_writer_add_ref(w, &refs[0]), REFTABLE_API_ERROR);
+
+	reftable_writer_close(w);
+	reftable_writer_free(w);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__write_multiple_indices(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 100,
+	};
+	struct reftable_buf writer_buf = REFTABLE_BUF_INIT;
+	struct reftable_block_source source = { 0 };
+	struct reftable_iterator it = { 0 };
+	const struct reftable_stats *stats;
+	struct reftable_writer *writer;
+	struct reftable_reader *reader;
+	char buf[128];
+	int i;
+
+	writer = cl_reftable_strbuf_writer(&writer_buf, &opts);
+	reftable_writer_set_limits(writer, 1, 1);
+	for (i = 0; i < 100; i++) {
+		struct reftable_ref_record ref = {
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = {i},
+		};
+
+		snprintf(buf, sizeof(buf), "refs/heads/%04d", i);
+		ref.refname = buf;
+
+		cl_assert(reftable_writer_add_ref(writer, &ref) == 0);
+	}
+
+	for (i = 0; i < 100; i++) {
+		struct reftable_log_record log = {
+			.update_index = 1,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value.update = {
+				.old_hash = { i },
+				.new_hash = { i },
+			},
+		};
+
+		snprintf(buf, sizeof(buf), "refs/heads/%04d", i);
+		log.refname = buf;
+
+		cl_assert(reftable_writer_add_log(writer, &log) == 0);
+	}
+
+	reftable_writer_close(writer);
+
+	/*
+	 * The written data should be sufficiently large to result in indices
+	 * for each of the block types.
+	 */
+	stats = reftable_writer_stats(writer);
+	cl_assert(stats->ref_stats.index_offset > 0);
+	cl_assert(stats->obj_stats.index_offset > 0);
+	cl_assert(stats->log_stats.index_offset > 0);
+
+	block_source_from_buf(&source, &writer_buf);
+	cl_assert(reftable_reader_new(&reader, &source, "filename") == 0);
+
+	/*
+	 * Seeking the log uses the log index now. In case there is any
+	 * confusion regarding indices we would notice here.
+	 */
+	cl_assert(reftable_reader_init_log_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_log(&it, "") == 0);
+
+	reftable_iterator_destroy(&it);
+	reftable_writer_free(writer);
+	reftable_reader_decref(reader);
+	reftable_buf_release(&writer_buf);
+}
+
+void test_reftable_readwrite__write_multi_level_index(void)
+{
+	struct reftable_write_options opts = {
+		.block_size = 100,
+	};
+	struct reftable_buf writer_buf = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
+	struct reftable_block_source source = { 0 };
+	struct reftable_iterator it = { 0 };
+	const struct reftable_stats *stats;
+	struct reftable_writer *writer;
+	struct reftable_reader *reader;
+
+	writer = cl_reftable_strbuf_writer(&writer_buf, &opts);
+	reftable_writer_set_limits(writer, 1, 1);
+	for (size_t i = 0; i < 200; i++) {
+		struct reftable_ref_record ref = {
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = {i},
+		};
+		char buf[128];
+
+		snprintf(buf, sizeof(buf), "refs/heads/%03" PRIuMAX, (uintmax_t)i);
+		ref.refname = buf;
+
+		cl_assert(reftable_writer_add_ref(writer, &ref) == 0);
+	}
+	reftable_writer_close(writer);
+
+	/*
+	 * The written refs should be sufficiently large to result in a
+	 * multi-level index.
+	 */
+	stats = reftable_writer_stats(writer);
+	cl_assert_equal_i(stats->ref_stats.max_index_level, 2);
+
+	block_source_from_buf(&source, &writer_buf);
+	cl_assert(reftable_reader_new(&reader, &source, "filename") == 0);
+
+	/*
+	 * Seeking the last ref should work as expected.
+	 */
+	cl_assert(reftable_reader_init_ref_iterator(reader, &it) == 0);
+	cl_assert(reftable_iterator_seek_ref(&it, "refs/heads/199") == 0);
+
+	reftable_iterator_destroy(&it);
+	reftable_writer_free(writer);
+	reftable_reader_decref(reader);
+	reftable_buf_release(&writer_buf);
+	reftable_buf_release(&buf);
+}
+
+void test_reftable_readwrite__corrupt_table_empty(void)
+{
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_block_source source = { 0 };
+	struct reftable_reader *reader;
+
+	block_source_from_buf(&source, &buf);
+	cl_assert_equal_i(reftable_reader_new(&reader, &source,
+										  "file.log"),REFTABLE_FORMAT_ERROR);
+}
+
+void test_reftable_readwrite__corrupt_table(void)
+{
+	uint8_t zeros[1024] = { 0 };
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct reftable_block_source source = { 0 };
+	struct reftable_reader *reader;
+	cl_assert(reftable_buf_add(&buf, zeros, sizeof(zeros)) == 0);
+
+	block_source_from_buf(&source, &buf);
+	cl_assert_equal_i(reftable_reader_new(&reader, &source,
+										  "file.log"), REFTABLE_FORMAT_ERROR);
+
+	reftable_buf_release(&buf);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 08/10] t/unit-tests: convert reftable record test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (6 preceding siblings ...)
  2025-04-29 17:52 ` [PATCH v2 07/10] t/unit-tests: convert reftable readwrite " Seyi Kuforiji
@ 2025-04-29 17:53 ` Seyi Kuforiji
  2025-04-29 17:53 ` [PATCH v2 09/10] t/unit-tests: convert reftable stack " Seyi Kuforiji
  2025-04-29 17:53 ` [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar Seyi Kuforiji
  9 siblings, 0 replies; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:53 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable record test file to use clar by using clar assertions
where necessary.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                         |   2 +-
 t/meson.build                    |   2 +-
 t/unit-tests/t-reftable-record.c | 585 -------------------------------
 t/unit-tests/u-reftable-record.c | 565 +++++++++++++++++++++++++++++
 4 files changed, 567 insertions(+), 587 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-record.c
 create mode 100644 t/unit-tests/u-reftable-record.c

diff --git a/Makefile b/Makefile
index 88b6851b37..cd9db9adf1 100644
--- a/Makefile
+++ b/Makefile
@@ -1368,6 +1368,7 @@ CLAR_TEST_SUITES += u-reftable-merged
 CLAR_TEST_SUITES += u-reftable-pq
 CLAR_TEST_SUITES += u-reftable-reader
 CLAR_TEST_SUITES += u-reftable-readwrite
+CLAR_TEST_SUITES += u-reftable-record
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1380,7 +1381,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-record
 UNIT_TEST_PROGRAMS += t-reftable-stack
 UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
diff --git a/t/meson.build b/t/meson.build
index 7722d177e2..756cb2a2dd 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -14,6 +14,7 @@ clar_test_suites = [
   'unit-tests/u-reftable-pq.c',
   'unit-tests/u-reftable-reader.c',
   'unit-tests/u-reftable-readwrite.c',
+  'unit-tests/u-reftable-record.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -60,7 +61,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-record.c',
   'unit-tests/t-reftable-stack.c',
 ]
 
diff --git a/t/unit-tests/t-reftable-record.c b/t/unit-tests/t-reftable-record.c
deleted file mode 100644
index 5954966373..0000000000
--- a/t/unit-tests/t-reftable-record.c
+++ /dev/null
@@ -1,585 +0,0 @@
-/*
-  Copyright 2020 Google LLC
-
-  Use of this source code is governed by a BSD-style
-  license that can be found in the LICENSE file or at
-  https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "test-lib.h"
-#include "reftable/basics.h"
-#include "reftable/constants.h"
-#include "reftable/record.h"
-
-static void t_copy(struct reftable_record *rec)
-{
-	struct reftable_record copy;
-	uint8_t typ;
-
-	typ = reftable_record_type(rec);
-	check(!reftable_record_init(&copy, typ));
-	reftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);
-	/* do it twice to catch memory leaks */
-	reftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);
-	check(reftable_record_equal(rec, &copy, REFTABLE_HASH_SIZE_SHA1));
-
-	reftable_record_release(&copy);
-}
-
-static void t_varint_roundtrip(void)
-{
-	uint64_t inputs[] = { 0,
-			      1,
-			      27,
-			      127,
-			      128,
-			      257,
-			      4096,
-			      ((uint64_t)1 << 63),
-			      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };
-
-	for (size_t i = 0; i < ARRAY_SIZE(inputs); i++) {
-		uint8_t dest[10];
-
-		struct string_view out = {
-			.buf = dest,
-			.len = sizeof(dest),
-		};
-		uint64_t in = inputs[i];
-		int n = put_var_int(&out, in);
-		uint64_t got = 0;
-
-		check_int(n, >, 0);
-		out.len = n;
-		n = get_var_int(&got, &out);
-		check_int(n, >, 0);
-
-		check_int(got, ==, in);
-	}
-}
-
-static void t_varint_overflow(void)
-{
-	unsigned char buf[] = {
-		0xFF, 0xFF, 0xFF, 0xFF,
-		0xFF, 0xFF, 0xFF, 0xFF,
-		0xFF, 0x00,
-	};
-	struct string_view view = {
-		.buf = buf,
-		.len = sizeof(buf),
-	};
-	uint64_t value;
-	int err = get_var_int(&value, &view);
-	check_int(err, ==, -1);
-}
-
-static void set_hash(uint8_t *h, int j)
-{
-	for (size_t i = 0; i < hash_size(REFTABLE_HASH_SHA1); i++)
-		h[i] = (j >> i) & 0xff;
-}
-
-static void t_reftable_ref_record_comparison(void)
-{
-	struct reftable_record in[3] = {
-		{
-			.type = BLOCK_TYPE_REF,
-			.u.ref.refname = (char *) "refs/heads/master",
-			.u.ref.value_type = REFTABLE_REF_VAL1,
-		},
-		{
-			.type = BLOCK_TYPE_REF,
-			.u.ref.refname = (char *) "refs/heads/master",
-			.u.ref.value_type = REFTABLE_REF_DELETION,
-		},
-		{
-			.type = BLOCK_TYPE_REF,
-			.u.ref.refname = (char *) "HEAD",
-			.u.ref.value_type = REFTABLE_REF_SYMREF,
-			.u.ref.value.symref = (char *) "refs/heads/master",
-		},
-	};
-	int cmp;
-
-	check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check(!cmp);
-
-	check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[1], &in[2], &cmp));
-	check_int(cmp, >, 0);
-
-	in[1].u.ref.value_type = in[0].u.ref.value_type;
-	check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check(!cmp);
-}
-
-static void t_reftable_ref_record_compare_name(void)
-{
-	struct reftable_ref_record recs[3] = {
-		{
-			.refname = (char *) "refs/heads/a"
-		},
-		{
-			.refname = (char *) "refs/heads/b"
-		},
-		{
-			.refname = (char *) "refs/heads/a"
-		},
-	};
-
-	check_int(reftable_ref_record_compare_name(&recs[0], &recs[1]), <, 0);
-	check_int(reftable_ref_record_compare_name(&recs[1], &recs[0]), >, 0);
-	check_int(reftable_ref_record_compare_name(&recs[0], &recs[2]), ==, 0);
-}
-
-static void t_reftable_ref_record_roundtrip(void)
-{
-	struct reftable_buf scratch = REFTABLE_BUF_INIT;
-
-	for (int i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
-		struct reftable_record in = {
-			.type = BLOCK_TYPE_REF,
-			.u.ref.value_type = i,
-		};
-		struct reftable_record out = { .type = BLOCK_TYPE_REF };
-		struct reftable_buf key = REFTABLE_BUF_INIT;
-		uint8_t buffer[1024] = { 0 };
-		struct string_view dest = {
-			.buf = buffer,
-			.len = sizeof(buffer),
-		};
-		int n, m;
-
-		in.u.ref.value_type = i;
-		switch (i) {
-		case REFTABLE_REF_DELETION:
-			break;
-		case REFTABLE_REF_VAL1:
-			set_hash(in.u.ref.value.val1, 1);
-			break;
-		case REFTABLE_REF_VAL2:
-			set_hash(in.u.ref.value.val2.value, 1);
-			set_hash(in.u.ref.value.val2.target_value, 2);
-			break;
-		case REFTABLE_REF_SYMREF:
-			in.u.ref.value.symref = xstrdup("target");
-			break;
-		}
-		in.u.ref.refname = xstrdup("refs/heads/master");
-
-		t_copy(&in);
-
-		check_int(reftable_record_val_type(&in), ==, i);
-		check_int(reftable_record_is_deletion(&in), ==, i == REFTABLE_REF_DELETION);
-
-		reftable_record_key(&in, &key);
-		n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
-		check_int(n, >, 0);
-
-		/* decode into a non-zero reftable_record to test for leaks. */
-		m = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch);
-		check_int(n, ==, m);
-
-		check(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
-						 REFTABLE_HASH_SIZE_SHA1));
-		reftable_record_release(&in);
-
-		reftable_buf_release(&key);
-		reftable_record_release(&out);
-	}
-
-	reftable_buf_release(&scratch);
-}
-
-static void t_reftable_log_record_comparison(void)
-{
-	struct reftable_record in[3] = {
-		{
-			.type = BLOCK_TYPE_LOG,
-			.u.log.refname = (char *) "refs/heads/master",
-			.u.log.update_index = 42,
-		},
-		{
-			.type = BLOCK_TYPE_LOG,
-			.u.log.refname = (char *) "refs/heads/master",
-			.u.log.update_index = 22,
-		},
-		{
-			.type = BLOCK_TYPE_LOG,
-			.u.log.refname = (char *) "refs/heads/main",
-			.u.log.update_index = 22,
-		},
-	};
-	int cmp;
-
-	check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[1], &in[2], &cmp));
-	check_int(cmp, >, 0);
-	/* comparison should be reversed for equal keys, because
-	 * comparison is now performed on the basis of update indices */
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check_int(cmp, <, 0);
-
-	in[1].u.log.update_index = in[0].u.log.update_index;
-	check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-}
-
-static void t_reftable_log_record_compare_key(void)
-{
-	struct reftable_log_record logs[3] = {
-		{
-			.refname = (char *) "refs/heads/a",
-			.update_index = 1,
-		},
-		{
-			.refname = (char *) "refs/heads/b",
-			.update_index = 2,
-		},
-		{
-			.refname = (char *) "refs/heads/a",
-			.update_index = 3,
-		},
-	};
-
-	check_int(reftable_log_record_compare_key(&logs[0], &logs[1]), <, 0);
-	check_int(reftable_log_record_compare_key(&logs[1], &logs[0]), >, 0);
-
-	logs[1].update_index = logs[0].update_index;
-	check_int(reftable_log_record_compare_key(&logs[0], &logs[1]), <, 0);
-
-	check_int(reftable_log_record_compare_key(&logs[0], &logs[2]), >, 0);
-	check_int(reftable_log_record_compare_key(&logs[2], &logs[0]), <, 0);
-	logs[2].update_index = logs[0].update_index;
-	check_int(reftable_log_record_compare_key(&logs[0], &logs[2]), ==, 0);
-}
-
-static void t_reftable_log_record_roundtrip(void)
-{
-	struct reftable_log_record in[] = {
-		{
-			.refname = xstrdup("refs/heads/master"),
-			.update_index = 42,
-			.value_type = REFTABLE_LOG_UPDATE,
-			.value = {
-				.update = {
-					.name = xstrdup("han-wen"),
-					.email = xstrdup("hanwen@google.com"),
-					.message = xstrdup("test"),
-					.time = 1577123507,
-					.tz_offset = 100,
-				},
-			}
-		},
-		{
-			.refname = xstrdup("refs/heads/master"),
-			.update_index = 22,
-			.value_type = REFTABLE_LOG_DELETION,
-		},
-		{
-			.refname = xstrdup("branch"),
-			.update_index = 33,
-			.value_type = REFTABLE_LOG_UPDATE,
-		}
-	};
-	struct reftable_buf scratch = REFTABLE_BUF_INIT;
-	set_hash(in[0].value.update.new_hash, 1);
-	set_hash(in[0].value.update.old_hash, 2);
-	set_hash(in[2].value.update.new_hash, 3);
-	set_hash(in[2].value.update.old_hash, 4);
-
-	check(!reftable_log_record_is_deletion(&in[0]));
-	check(reftable_log_record_is_deletion(&in[1]));
-	check(!reftable_log_record_is_deletion(&in[2]));
-
-	for (size_t i = 0; i < ARRAY_SIZE(in); i++) {
-		struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
-		struct reftable_buf key = REFTABLE_BUF_INIT;
-		uint8_t buffer[1024] = { 0 };
-		struct string_view dest = {
-			.buf = buffer,
-			.len = sizeof(buffer),
-		};
-		/* populate out, to check for leaks. */
-		struct reftable_record out = {
-			.type = BLOCK_TYPE_LOG,
-			.u.log = {
-				.refname = xstrdup("old name"),
-				.value_type = REFTABLE_LOG_UPDATE,
-				.value = {
-					.update = {
-						.name = xstrdup("old name"),
-						.email = xstrdup("old@email"),
-						.message = xstrdup("old message"),
-					},
-				},
-			},
-		};
-		int n, m, valtype;
-
-		rec.u.log = in[i];
-
-		t_copy(&rec);
-
-		reftable_record_key(&rec, &key);
-
-		n = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1);
-		check_int(n, >=, 0);
-		valtype = reftable_record_val_type(&rec);
-		m = reftable_record_decode(&out, key, valtype, dest,
-					   REFTABLE_HASH_SIZE_SHA1, &scratch);
-		check_int(n, ==, m);
-
-		check(reftable_log_record_equal(&in[i], &out.u.log,
-						 REFTABLE_HASH_SIZE_SHA1));
-		reftable_log_record_release(&in[i]);
-		reftable_buf_release(&key);
-		reftable_record_release(&out);
-	}
-
-	reftable_buf_release(&scratch);
-}
-
-static void t_key_roundtrip(void)
-{
-	uint8_t buffer[1024] = { 0 };
-	struct string_view dest = {
-		.buf = buffer,
-		.len = sizeof(buffer),
-	};
-	struct reftable_buf last_key = REFTABLE_BUF_INIT;
-	struct reftable_buf key = REFTABLE_BUF_INIT;
-	struct reftable_buf roundtrip = REFTABLE_BUF_INIT;
-	int restart;
-	uint8_t extra;
-	int n, m;
-	uint8_t rt_extra;
-
-	check(!reftable_buf_addstr(&last_key, "refs/heads/master"));
-	check(!reftable_buf_addstr(&key, "refs/tags/bla"));
-	extra = 6;
-	n = reftable_encode_key(&restart, dest, last_key, key, extra);
-	check(!restart);
-	check_int(n, >, 0);
-
-	check(!reftable_buf_addstr(&roundtrip, "refs/heads/master"));
-	m = reftable_decode_key(&roundtrip, &rt_extra, dest);
-	check_int(n, ==, m);
-	check(!reftable_buf_cmp(&key, &roundtrip));
-	check_int(rt_extra, ==, extra);
-
-	reftable_buf_release(&last_key);
-	reftable_buf_release(&key);
-	reftable_buf_release(&roundtrip);
-}
-
-static void t_reftable_obj_record_comparison(void)
-{
-
-	uint8_t id_bytes[] = { 0, 1, 2, 3, 4, 5, 6 };
-	uint64_t offsets[] = { 0, 16, 32, 48, 64, 80, 96, 112};
-	struct reftable_record in[3] = {
-		{
-			.type = BLOCK_TYPE_OBJ,
-			.u.obj.hash_prefix = id_bytes,
-			.u.obj.hash_prefix_len = 7,
-			.u.obj.offsets = offsets,
-			.u.obj.offset_len = 8,
-		},
-		{
-			.type = BLOCK_TYPE_OBJ,
-			.u.obj.hash_prefix = id_bytes,
-			.u.obj.hash_prefix_len = 7,
-			.u.obj.offsets = offsets,
-			.u.obj.offset_len = 5,
-		},
-		{
-			.type = BLOCK_TYPE_OBJ,
-			.u.obj.hash_prefix = id_bytes,
-			.u.obj.hash_prefix_len = 5,
-		},
-	};
-	int cmp;
-
-	check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check(!cmp);
-
-	check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[1], &in[2], &cmp));
-	check_int(cmp, >, 0);
-
-	in[1].u.obj.offset_len = in[0].u.obj.offset_len;
-	check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check(!cmp);
-}
-
-static void t_reftable_obj_record_roundtrip(void)
-{
-	uint8_t testHash1[REFTABLE_HASH_SIZE_SHA1] = { 1, 2, 3, 4, 0 };
-	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
-	struct reftable_obj_record recs[3] = {
-		{
-			.hash_prefix = testHash1,
-			.hash_prefix_len = 5,
-			.offsets = till9,
-			.offset_len = 3,
-		},
-		{
-			.hash_prefix = testHash1,
-			.hash_prefix_len = 5,
-			.offsets = till9,
-			.offset_len = 9,
-		},
-		{
-			.hash_prefix = testHash1,
-			.hash_prefix_len = 5,
-		},
-	};
-	struct reftable_buf scratch = REFTABLE_BUF_INIT;
-
-	for (size_t i = 0; i < ARRAY_SIZE(recs); i++) {
-		uint8_t buffer[1024] = { 0 };
-		struct string_view dest = {
-			.buf = buffer,
-			.len = sizeof(buffer),
-		};
-		struct reftable_record in = {
-			.type = BLOCK_TYPE_OBJ,
-			.u = {
-				.obj = recs[i],
-			},
-		};
-		struct reftable_buf key = REFTABLE_BUF_INIT;
-		struct reftable_record out = { .type = BLOCK_TYPE_OBJ };
-		int n, m;
-		uint8_t extra;
-
-		check(!reftable_record_is_deletion(&in));
-		t_copy(&in);
-		reftable_record_key(&in, &key);
-		n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
-		check_int(n, >, 0);
-		extra = reftable_record_val_type(&in);
-		m = reftable_record_decode(&out, key, extra, dest,
-					   REFTABLE_HASH_SIZE_SHA1, &scratch);
-		check_int(n, ==, m);
-
-		check(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1));
-		reftable_buf_release(&key);
-		reftable_record_release(&out);
-	}
-
-	reftable_buf_release(&scratch);
-}
-
-static void t_reftable_index_record_comparison(void)
-{
-	struct reftable_record in[3] = {
-		{
-			.type = BLOCK_TYPE_INDEX,
-			.u.idx.offset = 22,
-			.u.idx.last_key = REFTABLE_BUF_INIT,
-		},
-		{
-			.type = BLOCK_TYPE_INDEX,
-			.u.idx.offset = 32,
-			.u.idx.last_key = REFTABLE_BUF_INIT,
-		},
-		{
-			.type = BLOCK_TYPE_INDEX,
-			.u.idx.offset = 32,
-			.u.idx.last_key = REFTABLE_BUF_INIT,
-		},
-	};
-	int cmp;
-
-	check(!reftable_buf_addstr(&in[0].u.idx.last_key, "refs/heads/master"));
-	check(!reftable_buf_addstr(&in[1].u.idx.last_key, "refs/heads/master"));
-	check(!reftable_buf_addstr(&in[2].u.idx.last_key, "refs/heads/branch"));
-
-	check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check(!cmp);
-
-	check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[1], &in[2], &cmp));
-	check_int(cmp, >, 0);
-
-	in[1].u.idx.offset = in[0].u.idx.offset;
-	check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1));
-	check(!reftable_record_cmp(&in[0], &in[1], &cmp));
-	check(!cmp);
-
-	for (size_t i = 0; i < ARRAY_SIZE(in); i++)
-		reftable_record_release(&in[i]);
-}
-
-static void t_reftable_index_record_roundtrip(void)
-{
-	struct reftable_record in = {
-		.type = BLOCK_TYPE_INDEX,
-		.u.idx = {
-			.offset = 42,
-			.last_key = REFTABLE_BUF_INIT,
-		},
-	};
-	uint8_t buffer[1024] = { 0 };
-	struct string_view dest = {
-		.buf = buffer,
-		.len = sizeof(buffer),
-	};
-	struct reftable_buf scratch = REFTABLE_BUF_INIT;
-	struct reftable_buf key = REFTABLE_BUF_INIT;
-	struct reftable_record out = {
-		.type = BLOCK_TYPE_INDEX,
-		.u.idx = { .last_key = REFTABLE_BUF_INIT },
-	};
-	int n, m;
-	uint8_t extra;
-
-	check(!reftable_buf_addstr(&in.u.idx.last_key, "refs/heads/master"));
-	reftable_record_key(&in, &key);
-	t_copy(&in);
-
-	check(!reftable_record_is_deletion(&in));
-	check(!reftable_buf_cmp(&key, &in.u.idx.last_key));
-	n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
-	check_int(n, >, 0);
-
-	extra = reftable_record_val_type(&in);
-	m = reftable_record_decode(&out, key, extra, dest, REFTABLE_HASH_SIZE_SHA1,
-				   &scratch);
-	check_int(m, ==, n);
-
-	check(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1));
-
-	reftable_record_release(&out);
-	reftable_buf_release(&key);
-	reftable_buf_release(&scratch);
-	reftable_buf_release(&in.u.idx.last_key);
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_reftable_ref_record_comparison(), "comparison operations work on ref record");
-	TEST(t_reftable_log_record_comparison(), "comparison operations work on log record");
-	TEST(t_reftable_index_record_comparison(), "comparison operations work on index record");
-	TEST(t_reftable_obj_record_comparison(), "comparison operations work on obj record");
-	TEST(t_reftable_ref_record_compare_name(), "reftable_ref_record_compare_name works");
-	TEST(t_reftable_log_record_compare_key(), "reftable_log_record_compare_key works");
-	TEST(t_reftable_log_record_roundtrip(), "record operations work on log record");
-	TEST(t_reftable_ref_record_roundtrip(), "record operations work on ref record");
-	TEST(t_varint_roundtrip(), "put_var_int and get_var_int work");
-	TEST(t_varint_overflow(), "get_var_int notices an integer overflow");
-	TEST(t_key_roundtrip(), "reftable_encode_key and reftable_decode_key work");
-	TEST(t_reftable_obj_record_roundtrip(), "record operations work on obj record");
-	TEST(t_reftable_index_record_roundtrip(), "record operations work on index record");
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-record.c b/t/unit-tests/u-reftable-record.c
new file mode 100644
index 0000000000..ac2e33584c
--- /dev/null
+++ b/t/unit-tests/u-reftable-record.c
@@ -0,0 +1,565 @@
+/*
+  Copyright 2020 Google LLC
+
+  Use of this source code is governed by a BSD-style
+  license that can be found in the LICENSE file or at
+  https://developers.google.com/open-source/licenses/bsd
+*/
+
+#include "unit-test.h"
+#include "reftable/basics.h"
+#include "reftable/constants.h"
+#include "reftable/record.h"
+
+static void t_copy(struct reftable_record *rec)
+{
+	struct reftable_record copy;
+	uint8_t typ;
+
+	typ = reftable_record_type(rec);
+	cl_assert(reftable_record_init(&copy, typ) == 0);
+	reftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);
+	/* do it twice to catch memory leaks */
+	reftable_record_copy_from(&copy, rec, REFTABLE_HASH_SIZE_SHA1);
+	cl_assert(reftable_record_equal(rec, &copy, REFTABLE_HASH_SIZE_SHA1) != 0);
+
+	reftable_record_release(&copy);
+}
+
+void test_reftable_record__varint_roundtrip(void)
+{
+	uint64_t inputs[] = { 0,
+			      1,
+			      27,
+			      127,
+			      128,
+			      257,
+			      4096,
+			      ((uint64_t)1 << 63),
+			      ((uint64_t)1 << 63) + ((uint64_t)1 << 63) - 1 };
+
+	for (size_t i = 0; i < ARRAY_SIZE(inputs); i++) {
+		uint8_t dest[10];
+
+		struct string_view out = {
+			.buf = dest,
+			.len = sizeof(dest),
+		};
+		uint64_t in = inputs[i];
+		int n = put_var_int(&out, in);
+		uint64_t got = 0;
+
+		cl_assert(n > 0);
+		out.len = n;
+		n = get_var_int(&got, &out);
+		cl_assert(n > 0);
+
+		cl_assert_equal_i(got, in);
+	}
+}
+
+void test_reftable_record__varint_overflow(void)
+{
+	unsigned char buf[] = {
+		0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0x00,
+	};
+	struct string_view view = {
+		.buf = buf,
+		.len = sizeof(buf),
+	};
+	uint64_t value;
+	cl_assert_equal_i(get_var_int(&value, &view), -1);
+}
+
+static void set_hash(uint8_t *h, int j)
+{
+	for (size_t i = 0; i < hash_size(REFTABLE_HASH_SHA1); i++)
+		h[i] = (j >> i) & 0xff;
+}
+
+void test_reftable_record__ref_record_comparison(void)
+{
+	struct reftable_record in[3] = {
+		{
+			.type = BLOCK_TYPE_REF,
+			.u.ref.refname = (char *) "refs/heads/master",
+			.u.ref.value_type = REFTABLE_REF_VAL1,
+		},
+		{
+			.type = BLOCK_TYPE_REF,
+			.u.ref.refname = (char *) "refs/heads/master",
+			.u.ref.value_type = REFTABLE_REF_DELETION,
+		},
+		{
+			.type = BLOCK_TYPE_REF,
+			.u.ref.refname = (char *) "HEAD",
+			.u.ref.value_type = REFTABLE_REF_SYMREF,
+			.u.ref.value.symref = (char *) "refs/heads/master",
+		},
+	};
+	int cmp;
+
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp == 0);
+
+	cl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);
+	cl_assert(cmp > 0);
+
+	in[1].u.ref.value_type = in[0].u.ref.value_type;
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp == 0);
+}
+
+void test_reftable_record__ref_record_compare_name(void)
+{
+	struct reftable_ref_record recs[3] = {
+		{
+			.refname = (char *) "refs/heads/a"
+		},
+		{
+			.refname = (char *) "refs/heads/b"
+		},
+		{
+			.refname = (char *) "refs/heads/a"
+		},
+	};
+
+	cl_assert(reftable_ref_record_compare_name(&recs[0], &recs[1]) < 0);
+	cl_assert(reftable_ref_record_compare_name(&recs[1], &recs[0]) > 0);
+	cl_assert_equal_i(reftable_ref_record_compare_name(&recs[0], &recs[2]), 0);
+}
+
+void test_reftable_record__ref_record_roundtrip(void)
+{
+	struct reftable_buf scratch = REFTABLE_BUF_INIT;
+
+	for (int i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_REF,
+			.u.ref.value_type = i,
+		};
+		struct reftable_record out = { .type = BLOCK_TYPE_REF };
+		struct reftable_buf key = REFTABLE_BUF_INIT;
+		uint8_t buffer[1024] = { 0 };
+		struct string_view dest = {
+			.buf = buffer,
+			.len = sizeof(buffer),
+		};
+		int n, m;
+
+		in.u.ref.value_type = i;
+		switch (i) {
+		case REFTABLE_REF_DELETION:
+			break;
+		case REFTABLE_REF_VAL1:
+			set_hash(in.u.ref.value.val1, 1);
+			break;
+		case REFTABLE_REF_VAL2:
+			set_hash(in.u.ref.value.val2.value, 1);
+			set_hash(in.u.ref.value.val2.target_value, 2);
+			break;
+		case REFTABLE_REF_SYMREF:
+			in.u.ref.value.symref = xstrdup("target");
+			break;
+		}
+		in.u.ref.refname = xstrdup("refs/heads/master");
+
+		t_copy(&in);
+
+		cl_assert_equal_i(reftable_record_val_type(&in), i);
+		cl_assert_equal_i(reftable_record_is_deletion(&in),  i == REFTABLE_REF_DELETION);
+
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
+		cl_assert(n > 0);
+
+		/* decode into a non-zero reftable_record to test for leaks. */
+		m = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch);
+		cl_assert_equal_i(n, m);
+
+		cl_assert(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_record_release(&in);
+
+		reftable_buf_release(&key);
+		reftable_record_release(&out);
+	}
+
+	reftable_buf_release(&scratch);
+}
+
+void test_reftable_record__log_record_comparison(void)
+{
+	struct reftable_record in[3] = {
+		{
+			.type = BLOCK_TYPE_LOG,
+			.u.log.refname = (char *) "refs/heads/master",
+			.u.log.update_index = 42,
+		},
+		{
+			.type = BLOCK_TYPE_LOG,
+			.u.log.refname = (char *) "refs/heads/master",
+			.u.log.update_index = 22,
+		},
+		{
+			.type = BLOCK_TYPE_LOG,
+			.u.log.refname = (char *) "refs/heads/main",
+			.u.log.update_index = 22,
+		},
+	};
+	int cmp;
+
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);
+	cl_assert(cmp > 0);
+	/* comparison should be reversed for equal keys, because
+	 * comparison is now performed on the basis of update indices */
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp < 0);
+
+	in[1].u.log.update_index = in[0].u.log.update_index;
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+}
+
+void test_reftable_record__log_record_compare_key(void)
+{
+	struct reftable_log_record logs[3] = {
+		{
+			.refname = (char *) "refs/heads/a",
+			.update_index = 1,
+		},
+		{
+			.refname = (char *) "refs/heads/b",
+			.update_index = 2,
+		},
+		{
+			.refname = (char *) "refs/heads/a",
+			.update_index = 3,
+		},
+	};
+
+	cl_assert(reftable_log_record_compare_key(&logs[0], &logs[1]) < 0);
+	cl_assert(reftable_log_record_compare_key(&logs[1], &logs[0]) > 0);
+
+	logs[1].update_index = logs[0].update_index;
+	cl_assert(reftable_log_record_compare_key(&logs[0], &logs[1]) < 0);
+
+	cl_assert(reftable_log_record_compare_key(&logs[0], &logs[2]) > 0);
+	cl_assert(reftable_log_record_compare_key(&logs[2], &logs[0]) < 0);
+	logs[2].update_index = logs[0].update_index;
+	cl_assert_equal_i(reftable_log_record_compare_key(&logs[0], &logs[2]), 0);
+}
+
+void test_reftable_record__log_record_roundtrip(void)
+{
+	struct reftable_log_record in[] = {
+		{
+			.refname = xstrdup("refs/heads/master"),
+			.update_index = 42,
+			.value_type = REFTABLE_LOG_UPDATE,
+			.value = {
+				.update = {
+					.name = xstrdup("han-wen"),
+					.email = xstrdup("hanwen@google.com"),
+					.message = xstrdup("test"),
+					.time = 1577123507,
+					.tz_offset = 100,
+				},
+			}
+		},
+		{
+			.refname = xstrdup("refs/heads/master"),
+			.update_index = 22,
+			.value_type = REFTABLE_LOG_DELETION,
+		},
+		{
+			.refname = xstrdup("branch"),
+			.update_index = 33,
+			.value_type = REFTABLE_LOG_UPDATE,
+		}
+	};
+	struct reftable_buf scratch = REFTABLE_BUF_INIT;
+	set_hash(in[0].value.update.new_hash, 1);
+	set_hash(in[0].value.update.old_hash, 2);
+	set_hash(in[2].value.update.new_hash, 3);
+	set_hash(in[2].value.update.old_hash, 4);
+
+	cl_assert(reftable_log_record_is_deletion(&in[0]) == 0);
+	cl_assert(reftable_log_record_is_deletion(&in[1]) != 0);
+	cl_assert(reftable_log_record_is_deletion(&in[2]) == 0);
+
+	for (size_t i = 0; i < ARRAY_SIZE(in); i++) {
+		struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
+		struct reftable_buf key = REFTABLE_BUF_INIT;
+		uint8_t buffer[1024] = { 0 };
+		struct string_view dest = {
+			.buf = buffer,
+			.len = sizeof(buffer),
+		};
+		/* populate out, to check for leaks. */
+		struct reftable_record out = {
+			.type = BLOCK_TYPE_LOG,
+			.u.log = {
+				.refname = xstrdup("old name"),
+				.value_type = REFTABLE_LOG_UPDATE,
+				.value = {
+					.update = {
+						.name = xstrdup("old name"),
+						.email = xstrdup("old@email"),
+						.message = xstrdup("old message"),
+					},
+				},
+			},
+		};
+		int n, m, valtype;
+
+		rec.u.log = in[i];
+
+		t_copy(&rec);
+
+		reftable_record_key(&rec, &key);
+
+		n = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1);
+		cl_assert(n >= 0);
+		valtype = reftable_record_val_type(&rec);
+		m = reftable_record_decode(&out, key, valtype, dest,
+					   REFTABLE_HASH_SIZE_SHA1, &scratch);
+		cl_assert_equal_i(n, m);
+
+		cl_assert(reftable_log_record_equal(&in[i], &out.u.log,
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_log_record_release(&in[i]);
+		reftable_buf_release(&key);
+		reftable_record_release(&out);
+	}
+
+	reftable_buf_release(&scratch);
+}
+
+void test_reftable_record__key_roundtrip(void)
+{
+	uint8_t buffer[1024] = { 0 };
+	struct string_view dest = {
+		.buf = buffer,
+		.len = sizeof(buffer),
+	};
+	struct reftable_buf last_key = REFTABLE_BUF_INIT;
+	struct reftable_buf key = REFTABLE_BUF_INIT;
+	struct reftable_buf roundtrip = REFTABLE_BUF_INIT;
+	int restart;
+	uint8_t extra;
+	int n, m;
+	uint8_t rt_extra;
+
+	cl_assert(reftable_buf_addstr(&last_key, "refs/heads/master") == 0);
+	cl_assert(reftable_buf_addstr(&key, "refs/tags/bla") == 0);
+	extra = 6;
+	n = reftable_encode_key(&restart, dest, last_key, key, extra);
+	cl_assert(restart == 0);
+	cl_assert(n > 0);
+
+	cl_assert(reftable_buf_addstr(&roundtrip, "refs/heads/master") == 0);
+	m = reftable_decode_key(&roundtrip, &rt_extra, dest);
+	cl_assert_equal_i(n, m);
+	cl_assert(reftable_buf_cmp(&key, &roundtrip) == 0);
+	cl_assert_equal_i(rt_extra, extra);
+
+	reftable_buf_release(&last_key);
+	reftable_buf_release(&key);
+	reftable_buf_release(&roundtrip);
+}
+
+void test_reftable_record__obj_record_comparison(void)
+{
+
+	uint8_t id_bytes[] = { 0, 1, 2, 3, 4, 5, 6 };
+	uint64_t offsets[] = { 0, 16, 32, 48, 64, 80, 96, 112};
+	struct reftable_record in[3] = {
+		{
+			.type = BLOCK_TYPE_OBJ,
+			.u.obj.hash_prefix = id_bytes,
+			.u.obj.hash_prefix_len = 7,
+			.u.obj.offsets = offsets,
+			.u.obj.offset_len = 8,
+		},
+		{
+			.type = BLOCK_TYPE_OBJ,
+			.u.obj.hash_prefix = id_bytes,
+			.u.obj.hash_prefix_len = 7,
+			.u.obj.offsets = offsets,
+			.u.obj.offset_len = 5,
+		},
+		{
+			.type = BLOCK_TYPE_OBJ,
+			.u.obj.hash_prefix = id_bytes,
+			.u.obj.hash_prefix_len = 5,
+		},
+	};
+	int cmp;
+
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp == 0);
+
+	cl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);
+	cl_assert(cmp > 0);
+
+	in[1].u.obj.offset_len = in[0].u.obj.offset_len;
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp == 0);
+}
+
+void test_reftable_record__obj_record_roundtrip(void)
+{
+	uint8_t testHash1[REFTABLE_HASH_SIZE_SHA1] = { 1, 2, 3, 4, 0 };
+	uint64_t till9[] = { 1, 2, 3, 4, 500, 600, 700, 800, 9000 };
+	struct reftable_obj_record recs[3] = {
+		{
+			.hash_prefix = testHash1,
+			.hash_prefix_len = 5,
+			.offsets = till9,
+			.offset_len = 3,
+		},
+		{
+			.hash_prefix = testHash1,
+			.hash_prefix_len = 5,
+			.offsets = till9,
+			.offset_len = 9,
+		},
+		{
+			.hash_prefix = testHash1,
+			.hash_prefix_len = 5,
+		},
+	};
+	struct reftable_buf scratch = REFTABLE_BUF_INIT;
+
+	for (size_t i = 0; i < ARRAY_SIZE(recs); i++) {
+		uint8_t buffer[1024] = { 0 };
+		struct string_view dest = {
+			.buf = buffer,
+			.len = sizeof(buffer),
+		};
+		struct reftable_record in = {
+			.type = BLOCK_TYPE_OBJ,
+			.u = {
+				.obj = recs[i],
+			},
+		};
+		struct reftable_buf key = REFTABLE_BUF_INIT;
+		struct reftable_record out = { .type = BLOCK_TYPE_OBJ };
+		int n, m;
+		uint8_t extra;
+
+		cl_assert(reftable_record_is_deletion(&in) == 0);
+		t_copy(&in);
+		reftable_record_key(&in, &key);
+		n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
+		cl_assert(n > 0);
+		extra = reftable_record_val_type(&in);
+		m = reftable_record_decode(&out, key, extra, dest,
+					   REFTABLE_HASH_SIZE_SHA1, &scratch);
+		cl_assert_equal_i(n, m);
+
+		cl_assert(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_buf_release(&key);
+		reftable_record_release(&out);
+	}
+
+	reftable_buf_release(&scratch);
+}
+
+void test_reftable_record__index_record_comparison(void)
+{
+	struct reftable_record in[3] = {
+		{
+			.type = BLOCK_TYPE_INDEX,
+			.u.idx.offset = 22,
+			.u.idx.last_key = REFTABLE_BUF_INIT,
+		},
+		{
+			.type = BLOCK_TYPE_INDEX,
+			.u.idx.offset = 32,
+			.u.idx.last_key = REFTABLE_BUF_INIT,
+		},
+		{
+			.type = BLOCK_TYPE_INDEX,
+			.u.idx.offset = 32,
+			.u.idx.last_key = REFTABLE_BUF_INIT,
+		},
+	};
+	int cmp;
+
+	cl_assert(reftable_buf_addstr(&in[0].u.idx.last_key, "refs/heads/master") == 0);
+	cl_assert(reftable_buf_addstr(&in[1].u.idx.last_key, "refs/heads/master") == 0);
+	cl_assert(reftable_buf_addstr(&in[2].u.idx.last_key, "refs/heads/branch") == 0);
+
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp == 0);
+
+	cl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0);
+	cl_assert(reftable_record_cmp(&in[1], &in[2], &cmp) == 0);
+	cl_assert(cmp > 0);
+
+	in[1].u.idx.offset = in[0].u.idx.offset;
+	cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0);
+	cl_assert(reftable_record_cmp(&in[0], &in[1], &cmp) == 0);
+	cl_assert(cmp == 0);
+
+	for (size_t i = 0; i < ARRAY_SIZE(in); i++)
+		reftable_record_release(&in[i]);
+}
+
+void test_reftable_record__index_record_roundtrip(void)
+{
+	struct reftable_record in = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = {
+			.offset = 42,
+			.last_key = REFTABLE_BUF_INIT,
+		},
+	};
+	uint8_t buffer[1024] = { 0 };
+	struct string_view dest = {
+		.buf = buffer,
+		.len = sizeof(buffer),
+	};
+	struct reftable_buf scratch = REFTABLE_BUF_INIT;
+	struct reftable_buf key = REFTABLE_BUF_INIT;
+	struct reftable_record out = {
+		.type = BLOCK_TYPE_INDEX,
+		.u.idx = { .last_key = REFTABLE_BUF_INIT },
+	};
+	int n, m;
+	uint8_t extra;
+
+	cl_assert(reftable_buf_addstr(&in.u.idx.last_key, "refs/heads/master") == 0);
+	reftable_record_key(&in, &key);
+	t_copy(&in);
+
+	cl_assert(reftable_record_is_deletion(&in) == 0);
+	cl_assert(reftable_buf_cmp(&key, &in.u.idx.last_key) == 0);
+	n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1);
+	cl_assert(n > 0);
+
+	extra = reftable_record_val_type(&in);
+	m = reftable_record_decode(&out, key, extra, dest, REFTABLE_HASH_SIZE_SHA1,
+				   &scratch);
+	cl_assert_equal_i(m, n);
+
+	cl_assert(reftable_record_equal(&in, &out, REFTABLE_HASH_SIZE_SHA1) != 0);
+
+	reftable_record_release(&out);
+	reftable_buf_release(&key);
+	reftable_buf_release(&scratch);
+	reftable_buf_release(&in.u.idx.last_key);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (7 preceding siblings ...)
  2025-04-29 17:53 ` [PATCH v2 08/10] t/unit-tests: convert reftable record " Seyi Kuforiji
@ 2025-04-29 17:53 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  2025-04-29 17:53 ` [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar Seyi Kuforiji
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:53 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Adapt reftable stack test file to use clar by using clar assertions
where necessary.

This marks the end of all unit tests migrated away from the
`unit-tests/t-*.c` pattern, there are no longer any files matching that
glob. Remove the sanity check for `t-*.c` files to prevent Meson
configuration errors during CI and local builds.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                        |    2 +-
 t/meson.build                   |    3 +-
 t/unit-tests/t-reftable-stack.c | 1451 -------------------------------
 t/unit-tests/u-reftable-stack.c | 1247 ++++++++++++++++++++++++++
 4 files changed, 1249 insertions(+), 1454 deletions(-)
 delete mode 100644 t/unit-tests/t-reftable-stack.c
 create mode 100644 t/unit-tests/u-reftable-stack.c

diff --git a/Makefile b/Makefile
index cd9db9adf1..0b42893611 100644
--- a/Makefile
+++ b/Makefile
@@ -1369,6 +1369,7 @@ CLAR_TEST_SUITES += u-reftable-pq
 CLAR_TEST_SUITES += u-reftable-reader
 CLAR_TEST_SUITES += u-reftable-readwrite
 CLAR_TEST_SUITES += u-reftable-record
+CLAR_TEST_SUITES += u-reftable-stack
 CLAR_TEST_SUITES += u-reftable-tree
 CLAR_TEST_SUITES += u-strbuf
 CLAR_TEST_SUITES += u-strcmp-offset
@@ -1381,7 +1382,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
 
-UNIT_TEST_PROGRAMS += t-reftable-stack
 UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
diff --git a/t/meson.build b/t/meson.build
index 756cb2a2dd..8fa00fc9ef 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -15,6 +15,7 @@ clar_test_suites = [
   'unit-tests/u-reftable-reader.c',
   'unit-tests/u-reftable-readwrite.c',
   'unit-tests/u-reftable-record.c',
+  'unit-tests/u-reftable-stack.c',
   'unit-tests/u-reftable-tree.c',
   'unit-tests/u-strbuf.c',
   'unit-tests/u-strcmp-offset.c',
@@ -61,7 +62,6 @@ clar_unit_tests = executable('unit-tests',
 test('unit-tests', clar_unit_tests)
 
 unit_test_programs = [
-  'unit-tests/t-reftable-stack.c',
 ]
 
 foreach unit_test_program : unit_test_programs
@@ -1102,7 +1102,6 @@ integration_tests = [
 # sufficient to catch missing test suites in our CI though.
 foreach glob, tests : {
   't[0-9][0-9][0-9][0-9]-*.sh': integration_tests,
-  'unit-tests/t-*.c': unit_test_programs,
   'unit-tests/u-*.c': clar_test_suites,
 }
   actual_tests = run_command(shell, '-c', 'ls ' + glob,
diff --git a/t/unit-tests/t-reftable-stack.c b/t/unit-tests/t-reftable-stack.c
deleted file mode 100644
index c3f0059c34..0000000000
--- a/t/unit-tests/t-reftable-stack.c
+++ /dev/null
@@ -1,1451 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#define DISABLE_SIGN_COMPARE_WARNINGS
-
-#include "test-lib.h"
-#include "lib-reftable.h"
-#include "dir.h"
-#include "reftable/merged.h"
-#include "reftable/reader.h"
-#include "reftable/reftable-error.h"
-#include "reftable/stack.h"
-#include "strbuf.h"
-#include "tempfile.h"
-#include <dirent.h>
-
-static void clear_dir(const char *dirname)
-{
-	struct strbuf path = REFTABLE_BUF_INIT;
-	strbuf_addstr(&path, dirname);
-	remove_dir_recursively(&path, 0);
-	strbuf_release(&path);
-}
-
-static int count_dir_entries(const char *dirname)
-{
-	DIR *dir = opendir(dirname);
-	int len = 0;
-	struct dirent *d;
-	if (!dir)
-		return 0;
-
-	while ((d = readdir(dir))) {
-		/*
-		 * Besides skipping over "." and "..", we also need to
-		 * skip over other files that have a leading ".". This
-		 * is due to behaviour of NFS, which will rename files
-		 * to ".nfs*" to emulate delete-on-last-close.
-		 *
-		 * In any case this should be fine as the reftable
-		 * library will never write files with leading dots
-		 * anyway.
-		 */
-		if (starts_with(d->d_name, "."))
-			continue;
-		len++;
-	}
-	closedir(dir);
-	return len;
-}
-
-/*
- * Work linenumber into the tempdir, so we can see which tests forget to
- * cleanup.
- */
-static char *get_tmp_template(int linenumber)
-{
-	const char *tmp = getenv("TMPDIR");
-	static char template[1024];
-	snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
-		 tmp ? tmp : "/tmp", linenumber);
-	return template;
-}
-
-static char *get_tmp_dir(int linenumber)
-{
-	char *dir = get_tmp_template(linenumber);
-	check(mkdtemp(dir) != NULL);
-	return dir;
-}
-
-static void t_read_file(void)
-{
-	char *fn = get_tmp_template(__LINE__);
-	struct tempfile *tmp = mks_tempfile(fn);
-	int fd = get_tempfile_fd(tmp);
-	char out[1024] = "line1\n\nline2\nline3";
-	int n, err;
-	char **names = NULL;
-	const char *want[] = { "line1", "line2", "line3" };
-
-	check_int(fd, >, 0);
-	n = write_in_full(fd, out, strlen(out));
-	check_int(n, ==, strlen(out));
-	err = close(fd);
-	check_int(err, >=, 0);
-
-	err = read_lines(fn, &names);
-	check(!err);
-
-	for (size_t i = 0; names[i]; i++)
-		check_str(want[i], names[i]);
-	free_names(names);
-	(void) remove(fn);
-	delete_tempfile(&tmp);
-}
-
-static int write_test_ref(struct reftable_writer *wr, void *arg)
-{
-	struct reftable_ref_record *ref = arg;
-	check(!reftable_writer_set_limits(wr, ref->update_index,
-					  ref->update_index));
-	return reftable_writer_add_ref(wr, ref);
-}
-
-static void write_n_ref_tables(struct reftable_stack *st,
-			       size_t n)
-{
-	int disable_auto_compact;
-	int err;
-
-	disable_auto_compact = st->opts.disable_auto_compact;
-	st->opts.disable_auto_compact = 1;
-
-	for (size_t i = 0; i < n; i++) {
-		struct reftable_ref_record ref = {
-			.update_index = reftable_stack_next_update_index(st),
-			.value_type = REFTABLE_REF_VAL1,
-		};
-		char buf[128];
-
-		snprintf(buf, sizeof(buf), "refs/heads/branch-%04"PRIuMAX, (uintmax_t)i);
-		ref.refname = buf;
-		t_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);
-
-		err = reftable_stack_add(st, &write_test_ref, &ref);
-		check(!err);
-	}
-
-	st->opts.disable_auto_compact = disable_auto_compact;
-}
-
-struct write_log_arg {
-	struct reftable_log_record *log;
-	uint64_t update_index;
-};
-
-static int write_test_log(struct reftable_writer *wr, void *arg)
-{
-	struct write_log_arg *wla = arg;
-
-	check(!reftable_writer_set_limits(wr, wla->update_index,
-					  wla->update_index));
-	return reftable_writer_add_log(wr, wla->log);
-}
-
-static void t_reftable_stack_add_one(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_buf scratch = REFTABLE_BUF_INIT;
-	int mask = umask(002);
-	struct reftable_write_options opts = {
-		.default_permissions = 0660,
-	};
-	struct reftable_stack *st = NULL;
-	int err;
-	struct reftable_ref_record ref = {
-		.refname = (char *) "HEAD",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-	struct reftable_ref_record dest = { 0 };
-	struct stat stat_result = { 0 };
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_add(st, write_test_ref, &ref);
-	check(!err);
-
-	err = reftable_stack_read_ref(st, ref.refname, &dest);
-	check(!err);
-	check(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1));
-	check_int(st->readers_len, >, 0);
-
-#ifndef GIT_WINDOWS_NATIVE
-	check(!reftable_buf_addstr(&scratch, dir));
-	check(!reftable_buf_addstr(&scratch, "/tables.list"));
-	err = stat(scratch.buf, &stat_result);
-	check(!err);
-	check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
-
-	reftable_buf_reset(&scratch);
-	check(!reftable_buf_addstr(&scratch, dir));
-	check(!reftable_buf_addstr(&scratch, "/"));
-	/* do not try at home; not an external API for reftable. */
-	check(!reftable_buf_addstr(&scratch, st->readers[0]->name));
-	err = stat(scratch.buf, &stat_result);
-	check(!err);
-	check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
-#else
-	(void) stat_result;
-#endif
-
-	reftable_ref_record_release(&dest);
-	reftable_stack_destroy(st);
-	reftable_buf_release(&scratch);
-	clear_dir(dir);
-	umask(mask);
-}
-
-static void t_reftable_stack_uptodate(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st1 = NULL;
-	struct reftable_stack *st2 = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-
-	int err;
-	struct reftable_ref_record ref1 = {
-		.refname = (char *) "HEAD",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-	struct reftable_ref_record ref2 = {
-		.refname = (char *) "branch2",
-		.update_index = 2,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-
-
-	/* simulate multi-process access to the same stack
-	   by creating two stacks for the same directory.
-	 */
-	err = reftable_new_stack(&st1, dir, &opts);
-	check(!err);
-
-	err = reftable_new_stack(&st2, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_add(st1, write_test_ref, &ref1);
-	check(!err);
-
-	err = reftable_stack_add(st2, write_test_ref, &ref2);
-	check_int(err, ==, REFTABLE_OUTDATED_ERROR);
-
-	err = reftable_stack_reload(st2);
-	check(!err);
-
-	err = reftable_stack_add(st2, write_test_ref, &ref2);
-	check(!err);
-	reftable_stack_destroy(st1);
-	reftable_stack_destroy(st2);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_transaction_api(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	int err;
-	struct reftable_addition *add = NULL;
-
-	struct reftable_ref_record ref = {
-		.refname = (char *) "HEAD",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-	struct reftable_ref_record dest = { 0 };
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	reftable_addition_destroy(add);
-
-	err = reftable_stack_new_addition(&add, st, 0);
-	check(!err);
-
-	err = reftable_addition_add(add, write_test_ref, &ref);
-	check(!err);
-
-	err = reftable_addition_commit(add);
-	check(!err);
-
-	reftable_addition_destroy(add);
-
-	err = reftable_stack_read_ref(st, ref.refname, &dest);
-	check(!err);
-	check_int(REFTABLE_REF_SYMREF, ==, dest.value_type);
-	check(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1));
-
-	reftable_ref_record_release(&dest);
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_transaction_with_reload(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_stack *st1 = NULL, *st2 = NULL;
-	int err;
-	struct reftable_addition *add = NULL;
-	struct reftable_ref_record refs[2] = {
-		{
-			.refname = (char *) "refs/heads/a",
-			.update_index = 1,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { '1' },
-		},
-		{
-			.refname = (char *) "refs/heads/b",
-			.update_index = 2,
-			.value_type = REFTABLE_REF_VAL1,
-			.value.val1 = { '1' },
-		},
-	};
-	struct reftable_ref_record ref = { 0 };
-
-	err = reftable_new_stack(&st1, dir, NULL);
-	check(!err);
-	err = reftable_new_stack(&st2, dir, NULL);
-	check(!err);
-
-	err = reftable_stack_new_addition(&add, st1, 0);
-	check(!err);
-	err = reftable_addition_add(add, write_test_ref, &refs[0]);
-	check(!err);
-	err = reftable_addition_commit(add);
-	check(!err);
-	reftable_addition_destroy(add);
-
-	/*
-	 * The second stack is now outdated, which we should notice. We do not
-	 * create the addition and lock the stack by default, but allow the
-	 * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
-	 */
-	err = reftable_stack_new_addition(&add, st2, 0);
-	check_int(err, ==, REFTABLE_OUTDATED_ERROR);
-	err = reftable_stack_new_addition(&add, st2, REFTABLE_STACK_NEW_ADDITION_RELOAD);
-	check(!err);
-	err = reftable_addition_add(add, write_test_ref, &refs[1]);
-	check(!err);
-	err = reftable_addition_commit(add);
-	check(!err);
-	reftable_addition_destroy(add);
-
-	for (size_t i = 0; i < ARRAY_SIZE(refs); i++) {
-		err = reftable_stack_read_ref(st2, refs[i].refname, &ref);
-		check(!err);
-		check(reftable_ref_record_equal(&refs[i], &ref, REFTABLE_HASH_SIZE_SHA1));
-	}
-
-	reftable_ref_record_release(&ref);
-	reftable_stack_destroy(st1);
-	reftable_stack_destroy(st2);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_transaction_api_performs_auto_compaction(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = {0};
-	struct reftable_addition *add = NULL;
-	struct reftable_stack *st = NULL;
-	size_t n = 20;
-	int err;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (size_t i = 0; i <= n; i++) {
-		struct reftable_ref_record ref = {
-			.update_index = reftable_stack_next_update_index(st),
-			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = (char *) "master",
-		};
-		char name[100];
-
-		snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
-		ref.refname = name;
-
-		/*
-		 * Disable auto-compaction for all but the last runs. Like this
-		 * we can ensure that we indeed honor this setting and have
-		 * better control over when exactly auto compaction runs.
-		 */
-		st->opts.disable_auto_compact = i != n;
-
-		err = reftable_stack_new_addition(&add, st, 0);
-		check(!err);
-
-		err = reftable_addition_add(add, write_test_ref, &ref);
-		check(!err);
-
-		err = reftable_addition_commit(add);
-		check(!err);
-
-		reftable_addition_destroy(add);
-
-		/*
-		 * The stack length should grow continuously for all runs where
-		 * auto compaction is disabled. When enabled, we should merge
-		 * all tables in the stack.
-		 */
-		if (i != n)
-			check_int(st->merged->readers_len, ==, i + 1);
-		else
-			check_int(st->merged->readers_len, ==, 1);
-	}
-
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_auto_compaction_fails_gracefully(void)
-{
-	struct reftable_ref_record ref = {
-		.refname = (char *) "refs/heads/master",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_VAL1,
-		.value.val1 = {0x01},
-	};
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st;
-	struct reftable_buf table_path = REFTABLE_BUF_INIT;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_add(st, write_test_ref, &ref);
-	check(!err);
-	check_int(st->merged->readers_len, ==, 1);
-	check_int(st->stats.attempts, ==, 0);
-	check_int(st->stats.failures, ==, 0);
-
-	/*
-	 * Lock the newly written table such that it cannot be compacted.
-	 * Adding a new table to the stack should not be impacted by this, even
-	 * though auto-compaction will now fail.
-	 */
-	check(!reftable_buf_addstr(&table_path, dir));
-	check(!reftable_buf_addstr(&table_path, "/"));
-	check(!reftable_buf_addstr(&table_path, st->readers[0]->name));
-	check(!reftable_buf_addstr(&table_path, ".lock"));
-	write_file_buf(table_path.buf, "", 0);
-
-	ref.update_index = 2;
-	err = reftable_stack_add(st, write_test_ref, &ref);
-	check(!err);
-	check_int(st->merged->readers_len, ==, 2);
-	check_int(st->stats.attempts, ==, 1);
-	check_int(st->stats.failures, ==, 1);
-
-	reftable_stack_destroy(st);
-	reftable_buf_release(&table_path);
-	clear_dir(dir);
-}
-
-static int write_error(struct reftable_writer *wr UNUSED, void *arg)
-{
-	return *((int *)arg);
-}
-
-static void t_reftable_stack_update_index_check(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	int err;
-	struct reftable_ref_record ref1 = {
-		.refname = (char *) "name1",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-	struct reftable_ref_record ref2 = {
-		.refname = (char *) "name2",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_add(st, write_test_ref, &ref1);
-	check(!err);
-
-	err = reftable_stack_add(st, write_test_ref, &ref2);
-	check_int(err, ==, REFTABLE_API_ERROR);
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_lock_failure(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	int err, i;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-	for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) {
-		err = reftable_stack_add(st, write_error, &i);
-		check_int(err, ==, i);
-	}
-
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_add(void)
-{
-	int err = 0;
-	struct reftable_write_options opts = {
-		.exact_log_message = 1,
-		.default_permissions = 0660,
-		.disable_auto_compact = 1,
-	};
-	struct reftable_stack *st = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_ref_record refs[2] = { 0 };
-	struct reftable_log_record logs[2] = { 0 };
-	struct reftable_buf path = REFTABLE_BUF_INIT;
-	struct stat stat_result;
-	size_t i, N = ARRAY_SIZE(refs);
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (i = 0; i < N; i++) {
-		char buf[256];
-		snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
-		refs[i].refname = xstrdup(buf);
-		refs[i].update_index = i + 1;
-		refs[i].value_type = REFTABLE_REF_VAL1;
-		t_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);
-
-		logs[i].refname = xstrdup(buf);
-		logs[i].update_index = N + i + 1;
-		logs[i].value_type = REFTABLE_LOG_UPDATE;
-		logs[i].value.update.email = xstrdup("identity@invalid");
-		t_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);
-	}
-
-	for (i = 0; i < N; i++) {
-		int err = reftable_stack_add(st, write_test_ref, &refs[i]);
-		check(!err);
-	}
-
-	for (i = 0; i < N; i++) {
-		struct write_log_arg arg = {
-			.log = &logs[i],
-			.update_index = reftable_stack_next_update_index(st),
-		};
-		int err = reftable_stack_add(st, write_test_log, &arg);
-		check(!err);
-	}
-
-	err = reftable_stack_compact_all(st, NULL);
-	check(!err);
-
-	for (i = 0; i < N; i++) {
-		struct reftable_ref_record dest = { 0 };
-
-		int err = reftable_stack_read_ref(st, refs[i].refname, &dest);
-		check(!err);
-		check(reftable_ref_record_equal(&dest, refs + i,
-						 REFTABLE_HASH_SIZE_SHA1));
-		reftable_ref_record_release(&dest);
-	}
-
-	for (i = 0; i < N; i++) {
-		struct reftable_log_record dest = { 0 };
-		int err = reftable_stack_read_log(st, refs[i].refname, &dest);
-		check(!err);
-		check(reftable_log_record_equal(&dest, logs + i,
-						 REFTABLE_HASH_SIZE_SHA1));
-		reftable_log_record_release(&dest);
-	}
-
-#ifndef GIT_WINDOWS_NATIVE
-	check(!reftable_buf_addstr(&path, dir));
-	check(!reftable_buf_addstr(&path, "/tables.list"));
-	err = stat(path.buf, &stat_result);
-	check(!err);
-	check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
-
-	reftable_buf_reset(&path);
-	check(!reftable_buf_addstr(&path, dir));
-	check(!reftable_buf_addstr(&path, "/"));
-	/* do not try at home; not an external API for reftable. */
-	check(!reftable_buf_addstr(&path, st->readers[0]->name));
-	err = stat(path.buf, &stat_result);
-	check(!err);
-	check_int((stat_result.st_mode & 0777), ==, opts.default_permissions);
-#else
-	(void) stat_result;
-#endif
-
-	/* cleanup */
-	reftable_stack_destroy(st);
-	for (i = 0; i < N; i++) {
-		reftable_ref_record_release(&refs[i]);
-		reftable_log_record_release(&logs[i]);
-	}
-	reftable_buf_release(&path);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_iterator(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_ref_record refs[10] = { 0 };
-	struct reftable_log_record logs[10] = { 0 };
-	struct reftable_iterator it = { 0 };
-	size_t N = ARRAY_SIZE(refs), i;
-	int err;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (i = 0; i < N; i++) {
-		refs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
-		refs[i].update_index = i + 1;
-		refs[i].value_type = REFTABLE_REF_VAL1;
-		t_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);
-
-		logs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
-		logs[i].update_index = i + 1;
-		logs[i].value_type = REFTABLE_LOG_UPDATE;
-		logs[i].value.update.email = xstrdup("johndoe@invalid");
-		logs[i].value.update.message = xstrdup("commit\n");
-		t_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);
-	}
-
-	for (i = 0; i < N; i++) {
-		err = reftable_stack_add(st, write_test_ref, &refs[i]);
-		check(!err);
-	}
-
-	for (i = 0; i < N; i++) {
-		struct write_log_arg arg = {
-			.log = &logs[i],
-			.update_index = reftable_stack_next_update_index(st),
-		};
-
-		err = reftable_stack_add(st, write_test_log, &arg);
-		check(!err);
-	}
-
-	reftable_stack_init_ref_iterator(st, &it);
-	reftable_iterator_seek_ref(&it, refs[0].refname);
-	for (i = 0; ; i++) {
-		struct reftable_ref_record ref = { 0 };
-
-		err = reftable_iterator_next_ref(&it, &ref);
-		if (err > 0)
-			break;
-		check(!err);
-		check(reftable_ref_record_equal(&ref, &refs[i], REFTABLE_HASH_SIZE_SHA1));
-		reftable_ref_record_release(&ref);
-	}
-	check_int(i, ==, N);
-
-	reftable_iterator_destroy(&it);
-
-	err = reftable_stack_init_log_iterator(st, &it);
-	check(!err);
-
-	reftable_iterator_seek_log(&it, logs[0].refname);
-	for (i = 0; ; i++) {
-		struct reftable_log_record log = { 0 };
-
-		err = reftable_iterator_next_log(&it, &log);
-		if (err > 0)
-			break;
-		check(!err);
-		check(reftable_log_record_equal(&log, &logs[i], REFTABLE_HASH_SIZE_SHA1));
-		reftable_log_record_release(&log);
-	}
-	check_int(i, ==, N);
-
-	reftable_stack_destroy(st);
-	reftable_iterator_destroy(&it);
-	for (i = 0; i < N; i++) {
-		reftable_ref_record_release(&refs[i]);
-		reftable_log_record_release(&logs[i]);
-	}
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_log_normalize(void)
-{
-	int err = 0;
-	struct reftable_write_options opts = {
-		0,
-	};
-	struct reftable_stack *st = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_log_record input = {
-		.refname = (char *) "branch",
-		.update_index = 1,
-		.value_type = REFTABLE_LOG_UPDATE,
-		.value = {
-			.update = {
-				.new_hash = { 1 },
-				.old_hash = { 2 },
-			},
-		},
-	};
-	struct reftable_log_record dest = {
-		.update_index = 0,
-	};
-	struct write_log_arg arg = {
-		.log = &input,
-		.update_index = 1,
-	};
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	input.value.update.message = (char *) "one\ntwo";
-	err = reftable_stack_add(st, write_test_log, &arg);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	input.value.update.message = (char *) "one";
-	err = reftable_stack_add(st, write_test_log, &arg);
-	check(!err);
-
-	err = reftable_stack_read_log(st, input.refname, &dest);
-	check(!err);
-	check_str(dest.value.update.message, "one\n");
-
-	input.value.update.message = (char *) "two\n";
-	arg.update_index = 2;
-	err = reftable_stack_add(st, write_test_log, &arg);
-	check(!err);
-	err = reftable_stack_read_log(st, input.refname, &dest);
-	check(!err);
-	check_str(dest.value.update.message, "two\n");
-
-	/* cleanup */
-	reftable_stack_destroy(st);
-	reftable_log_record_release(&dest);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_tombstone(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	int err;
-	struct reftable_ref_record refs[2] = { 0 };
-	struct reftable_log_record logs[2] = { 0 };
-	size_t i, N = ARRAY_SIZE(refs);
-	struct reftable_ref_record dest = { 0 };
-	struct reftable_log_record log_dest = { 0 };
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	/* even entries add the refs, odd entries delete them. */
-	for (i = 0; i < N; i++) {
-		const char *buf = "branch";
-		refs[i].refname = xstrdup(buf);
-		refs[i].update_index = i + 1;
-		if (i % 2 == 0) {
-			refs[i].value_type = REFTABLE_REF_VAL1;
-			t_reftable_set_hash(refs[i].value.val1, i,
-					    REFTABLE_HASH_SHA1);
-		}
-
-		logs[i].refname = xstrdup(buf);
-		/*
-		 * update_index is part of the key so should be constant.
-		 * The value itself should be less than the writer's upper
-		 * limit.
-		 */
-		logs[i].update_index = 1;
-		if (i % 2 == 0) {
-			logs[i].value_type = REFTABLE_LOG_UPDATE;
-			t_reftable_set_hash(logs[i].value.update.new_hash, i,
-					    REFTABLE_HASH_SHA1);
-			logs[i].value.update.email =
-				xstrdup("identity@invalid");
-		}
-	}
-	for (i = 0; i < N; i++) {
-		int err = reftable_stack_add(st, write_test_ref, &refs[i]);
-		check(!err);
-	}
-
-	for (i = 0; i < N; i++) {
-		struct write_log_arg arg = {
-			.log = &logs[i],
-			.update_index = reftable_stack_next_update_index(st),
-		};
-		int err = reftable_stack_add(st, write_test_log, &arg);
-		check(!err);
-	}
-
-	err = reftable_stack_read_ref(st, "branch", &dest);
-	check_int(err, ==, 1);
-	reftable_ref_record_release(&dest);
-
-	err = reftable_stack_read_log(st, "branch", &log_dest);
-	check_int(err, ==, 1);
-	reftable_log_record_release(&log_dest);
-
-	err = reftable_stack_compact_all(st, NULL);
-	check(!err);
-
-	err = reftable_stack_read_ref(st, "branch", &dest);
-	check_int(err, ==, 1);
-
-	err = reftable_stack_read_log(st, "branch", &log_dest);
-	check_int(err, ==, 1);
-	reftable_ref_record_release(&dest);
-	reftable_log_record_release(&log_dest);
-
-	/* cleanup */
-	reftable_stack_destroy(st);
-	for (i = 0; i < N; i++) {
-		reftable_ref_record_release(&refs[i]);
-		reftable_log_record_release(&logs[i]);
-	}
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_hash_id(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	int err;
-
-	struct reftable_ref_record ref = {
-		.refname = (char *) "master",
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "target",
-		.update_index = 1,
-	};
-	struct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };
-	struct reftable_stack *st32 = NULL;
-	struct reftable_write_options opts_default = { 0 };
-	struct reftable_stack *st_default = NULL;
-	struct reftable_ref_record dest = { 0 };
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_add(st, write_test_ref, &ref);
-	check(!err);
-
-	/* can't read it with the wrong hash ID. */
-	err = reftable_new_stack(&st32, dir, &opts32);
-	check_int(err, ==, REFTABLE_FORMAT_ERROR);
-
-	/* check that we can read it back with default opts too. */
-	err = reftable_new_stack(&st_default, dir, &opts_default);
-	check(!err);
-
-	err = reftable_stack_read_ref(st_default, "master", &dest);
-	check(!err);
-
-	check(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1));
-	reftable_ref_record_release(&dest);
-	reftable_stack_destroy(st);
-	reftable_stack_destroy(st_default);
-	clear_dir(dir);
-}
-
-static void t_suggest_compaction_segment(void)
-{
-	uint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };
-	struct segment min =
-		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
-	check_int(min.start, ==, 1);
-	check_int(min.end, ==, 10);
-}
-
-static void t_suggest_compaction_segment_nothing(void)
-{
-	uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
-	struct segment result =
-		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
-	check_int(result.start, ==, result.end);
-}
-
-static void t_reflog_expire(void)
-{
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	struct reftable_log_record logs[20] = { 0 };
-	size_t i, N = ARRAY_SIZE(logs) - 1;
-	int err;
-	struct reftable_log_expiry_config expiry = {
-		.time = 10,
-	};
-	struct reftable_log_record log = { 0 };
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (i = 1; i <= N; i++) {
-		char buf[256];
-		snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
-
-		logs[i].refname = xstrdup(buf);
-		logs[i].update_index = i;
-		logs[i].value_type = REFTABLE_LOG_UPDATE;
-		logs[i].value.update.time = i;
-		logs[i].value.update.email = xstrdup("identity@invalid");
-		t_reftable_set_hash(logs[i].value.update.new_hash, i,
-				    REFTABLE_HASH_SHA1);
-	}
-
-	for (i = 1; i <= N; i++) {
-		struct write_log_arg arg = {
-			.log = &logs[i],
-			.update_index = reftable_stack_next_update_index(st),
-		};
-		int err = reftable_stack_add(st, write_test_log, &arg);
-		check(!err);
-	}
-
-	err = reftable_stack_compact_all(st, NULL);
-	check(!err);
-
-	err = reftable_stack_compact_all(st, &expiry);
-	check(!err);
-
-	err = reftable_stack_read_log(st, logs[9].refname, &log);
-	check_int(err, ==, 1);
-
-	err = reftable_stack_read_log(st, logs[11].refname, &log);
-	check(!err);
-
-	expiry.min_update_index = 15;
-	err = reftable_stack_compact_all(st, &expiry);
-	check(!err);
-
-	err = reftable_stack_read_log(st, logs[14].refname, &log);
-	check_int(err, ==, 1);
-
-	err = reftable_stack_read_log(st, logs[16].refname, &log);
-	check(!err);
-
-	/* cleanup */
-	reftable_stack_destroy(st);
-	for (i = 0; i <= N; i++)
-		reftable_log_record_release(&logs[i]);
-	clear_dir(dir);
-	reftable_log_record_release(&log);
-}
-
-static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)
-{
-	check(!reftable_writer_set_limits(wr, 1, 1));
-	return 0;
-}
-
-static void t_empty_add(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	int err;
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_stack *st2 = NULL;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_add(st, write_nothing, NULL);
-	check(!err);
-
-	err = reftable_new_stack(&st2, dir, &opts);
-	check(!err);
-	clear_dir(dir);
-	reftable_stack_destroy(st);
-	reftable_stack_destroy(st2);
-}
-
-static int fastlogN(uint64_t sz, uint64_t N)
-{
-	int l = 0;
-	if (sz == 0)
-		return 0;
-	for (; sz; sz /= N)
-		l++;
-	return l - 1;
-}
-
-static void t_reftable_stack_auto_compaction(void)
-{
-	struct reftable_write_options opts = {
-		.disable_auto_compact = 1,
-	};
-	struct reftable_stack *st = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-	size_t i, N = 100;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (i = 0; i < N; i++) {
-		char name[100];
-		struct reftable_ref_record ref = {
-			.refname = name,
-			.update_index = reftable_stack_next_update_index(st),
-			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = (char *) "master",
-		};
-		snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
-
-		err = reftable_stack_add(st, write_test_ref, &ref);
-		check(!err);
-
-		err = reftable_stack_auto_compact(st);
-		check(!err);
-		check(i < 2 || st->merged->readers_len < 2 * fastlogN(i, 2));
-	}
-
-	check_int(reftable_stack_compaction_stats(st)->entries_written, <,
-	       (uint64_t)(N * fastlogN(N, 2)));
-
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_auto_compaction_factor(void)
-{
-	struct reftable_write_options opts = {
-		.auto_compaction_factor = 5,
-	};
-	struct reftable_stack *st = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-	size_t N = 100;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (size_t i = 0; i < N; i++) {
-		char name[20];
-		struct reftable_ref_record ref = {
-			.refname = name,
-			.update_index = reftable_stack_next_update_index(st),
-			.value_type = REFTABLE_REF_VAL1,
-		};
-		xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
-
-		err = reftable_stack_add(st, &write_test_ref, &ref);
-		check(!err);
-
-		check(i < 5 || st->merged->readers_len < 5 * fastlogN(i, 5));
-	}
-
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_auto_compaction_with_locked_tables(void)
-{
-	struct reftable_write_options opts = {
-		.disable_auto_compact = 1,
-	};
-	struct reftable_stack *st = NULL;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	write_n_ref_tables(st, 5);
-	check_int(st->merged->readers_len, ==, 5);
-
-	/*
-	 * Given that all tables we have written should be roughly the same
-	 * size, we expect that auto-compaction will want to compact all of the
-	 * tables. Locking any of the tables will keep it from doing so.
-	 */
-	check(!reftable_buf_addstr(&buf, dir));
-	check(!reftable_buf_addstr(&buf, "/"));
-	check(!reftable_buf_addstr(&buf, st->readers[2]->name));
-	check(!reftable_buf_addstr(&buf, ".lock"));
-	write_file_buf(buf.buf, "", 0);
-
-	/*
-	 * When parts of the stack are locked, then auto-compaction does a best
-	 * effort compaction of those tables which aren't locked. So while this
-	 * would in theory compact all tables, due to the preexisting lock we
-	 * only compact the newest two tables.
-	 */
-	err = reftable_stack_auto_compact(st);
-	check(!err);
-	check_int(st->stats.failures, ==, 0);
-	check_int(st->merged->readers_len, ==, 4);
-
-	reftable_stack_destroy(st);
-	reftable_buf_release(&buf);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_add_performs_auto_compaction(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-	size_t i, n = 20;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	for (i = 0; i <= n; i++) {
-		struct reftable_ref_record ref = {
-			.update_index = reftable_stack_next_update_index(st),
-			.value_type = REFTABLE_REF_SYMREF,
-			.value.symref = (char *) "master",
-		};
-		char buf[128];
-
-		/*
-		 * Disable auto-compaction for all but the last runs. Like this
-		 * we can ensure that we indeed honor this setting and have
-		 * better control over when exactly auto compaction runs.
-		 */
-		st->opts.disable_auto_compact = i != n;
-
-		snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i);
-		ref.refname = buf;
-
-		err = reftable_stack_add(st, write_test_ref, &ref);
-		check(!err);
-
-		/*
-		 * The stack length should grow continuously for all runs where
-		 * auto compaction is disabled. When enabled, we should merge
-		 * all tables in the stack.
-		 */
-		if (i != n)
-			check_int(st->merged->readers_len, ==, i + 1);
-		else
-			check_int(st->merged->readers_len, ==, 1);
-	}
-
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_compaction_with_locked_tables(void)
-{
-	struct reftable_write_options opts = {
-		.disable_auto_compact = 1,
-	};
-	struct reftable_stack *st = NULL;
-	struct reftable_buf buf = REFTABLE_BUF_INIT;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	write_n_ref_tables(st, 3);
-	check_int(st->merged->readers_len, ==, 3);
-
-	/* Lock one of the tables that we're about to compact. */
-	check(!reftable_buf_addstr(&buf, dir));
-	check(!reftable_buf_addstr(&buf, "/"));
-	check(!reftable_buf_addstr(&buf, st->readers[1]->name));
-	check(!reftable_buf_addstr(&buf, ".lock"));
-	write_file_buf(buf.buf, "", 0);
-
-	/*
-	 * Compaction is expected to fail given that we were not able to
-	 * compact all tables.
-	 */
-	err = reftable_stack_compact_all(st, NULL);
-	check_int(err, ==, REFTABLE_LOCK_ERROR);
-	check_int(st->stats.failures, ==, 1);
-	check_int(st->merged->readers_len, ==, 3);
-
-	reftable_stack_destroy(st);
-	reftable_buf_release(&buf);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_compaction_concurrent(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st1 = NULL, *st2 = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	err = reftable_new_stack(&st1, dir, &opts);
-	check(!err);
-	write_n_ref_tables(st1, 3);
-
-	err = reftable_new_stack(&st2, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_compact_all(st1, NULL);
-	check(!err);
-
-	reftable_stack_destroy(st1);
-	reftable_stack_destroy(st2);
-
-	check_int(count_dir_entries(dir), ==, 2);
-	clear_dir(dir);
-}
-
-static void unclean_stack_close(struct reftable_stack *st)
-{
-	/* break abstraction boundary to simulate unclean shutdown. */
-	for (size_t i = 0; i < st->readers_len; i++)
-		reftable_reader_decref(st->readers[i]);
-	st->readers_len = 0;
-	REFTABLE_FREE_AND_NULL(st->readers);
-}
-
-static void t_reftable_stack_compaction_concurrent_clean(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	err = reftable_new_stack(&st1, dir, &opts);
-	check(!err);
-	write_n_ref_tables(st1, 3);
-
-	err = reftable_new_stack(&st2, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_compact_all(st1, NULL);
-	check(!err);
-
-	unclean_stack_close(st1);
-	unclean_stack_close(st2);
-
-	err = reftable_new_stack(&st3, dir, &opts);
-	check(!err);
-
-	err = reftable_stack_clean(st3);
-	check(!err);
-	check_int(count_dir_entries(dir), ==, 2);
-
-	reftable_stack_destroy(st1);
-	reftable_stack_destroy(st2);
-	reftable_stack_destroy(st3);
-
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_read_across_reload(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st1 = NULL, *st2 = NULL;
-	struct reftable_ref_record rec = { 0 };
-	struct reftable_iterator it = { 0 };
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	/* Create a first stack and set up an iterator for it. */
-	err = reftable_new_stack(&st1, dir, &opts);
-	check(!err);
-	write_n_ref_tables(st1, 2);
-	check_int(st1->merged->readers_len, ==, 2);
-	reftable_stack_init_ref_iterator(st1, &it);
-	err = reftable_iterator_seek_ref(&it, "");
-	check(!err);
-
-	/* Set up a second stack for the same directory and compact it. */
-	err = reftable_new_stack(&st2, dir, &opts);
-	check(!err);
-	check_int(st2->merged->readers_len, ==, 2);
-	err = reftable_stack_compact_all(st2, NULL);
-	check(!err);
-	check_int(st2->merged->readers_len, ==, 1);
-
-	/*
-	 * Verify that we can continue to use the old iterator even after we
-	 * have reloaded its stack.
-	 */
-	err = reftable_stack_reload(st1);
-	check(!err);
-	check_int(st1->merged->readers_len, ==, 1);
-	err = reftable_iterator_next_ref(&it, &rec);
-	check(!err);
-	check_str(rec.refname, "refs/heads/branch-0000");
-	err = reftable_iterator_next_ref(&it, &rec);
-	check(!err);
-	check_str(rec.refname, "refs/heads/branch-0001");
-	err = reftable_iterator_next_ref(&it, &rec);
-	check_int(err, >, 0);
-
-	reftable_ref_record_release(&rec);
-	reftable_iterator_destroy(&it);
-	reftable_stack_destroy(st1);
-	reftable_stack_destroy(st2);
-	clear_dir(dir);
-}
-
-static void t_reftable_stack_reload_with_missing_table(void)
-{
-	struct reftable_write_options opts = { 0 };
-	struct reftable_stack *st = NULL;
-	struct reftable_ref_record rec = { 0 };
-	struct reftable_iterator it = { 0 };
-	struct reftable_buf table_path = REFTABLE_BUF_INIT, content = REFTABLE_BUF_INIT;
-	char *dir = get_tmp_dir(__LINE__);
-	int err;
-
-	/* Create a first stack and set up an iterator for it. */
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-	write_n_ref_tables(st, 2);
-	check_int(st->merged->readers_len, ==, 2);
-	reftable_stack_init_ref_iterator(st, &it);
-	err = reftable_iterator_seek_ref(&it, "");
-	check(!err);
-
-	/*
-	 * Update the tables.list file with some garbage data, while reusing
-	 * our old readers. This should trigger a partial reload of the stack,
-	 * where we try to reuse our old readers.
-	*/
-	check(!reftable_buf_addstr(&content, st->readers[0]->name));
-	check(!reftable_buf_addstr(&content, "\n"));
-	check(!reftable_buf_addstr(&content, st->readers[1]->name));
-	check(!reftable_buf_addstr(&content, "\n"));
-	check(!reftable_buf_addstr(&content, "garbage\n"));
-	check(!reftable_buf_addstr(&table_path, st->list_file));
-	check(!reftable_buf_addstr(&table_path, ".lock"));
-	write_file_buf(table_path.buf, content.buf, content.len);
-	err = rename(table_path.buf, st->list_file);
-	check(!err);
-
-	err = reftable_stack_reload(st);
-	check_int(err, ==, -4);
-	check_int(st->merged->readers_len, ==, 2);
-
-	/*
-	 * Even though the reload has failed, we should be able to continue
-	 * using the iterator.
-	*/
-	err = reftable_iterator_next_ref(&it, &rec);
-	check(!err);
-	check_str(rec.refname, "refs/heads/branch-0000");
-	err = reftable_iterator_next_ref(&it, &rec);
-	check(!err);
-	check_str(rec.refname, "refs/heads/branch-0001");
-	err = reftable_iterator_next_ref(&it, &rec);
-	check_int(err, >, 0);
-
-	reftable_ref_record_release(&rec);
-	reftable_iterator_destroy(&it);
-	reftable_stack_destroy(st);
-	reftable_buf_release(&table_path);
-	reftable_buf_release(&content);
-	clear_dir(dir);
-}
-
-static int write_limits_after_ref(struct reftable_writer *wr, void *arg)
-{
-	struct reftable_ref_record *ref = arg;
-	check(!reftable_writer_set_limits(wr, ref->update_index, ref->update_index));
-	check(!reftable_writer_add_ref(wr, ref));
-	return reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
-}
-
-static void t_reftable_invalid_limit_updates(void)
-{
-	struct reftable_ref_record ref = {
-		.refname = (char *) "HEAD",
-		.update_index = 1,
-		.value_type = REFTABLE_REF_SYMREF,
-		.value.symref = (char *) "master",
-	};
-	struct reftable_write_options opts = {
-		.default_permissions = 0660,
-	};
-	struct reftable_addition *add = NULL;
-	char *dir = get_tmp_dir(__LINE__);
-	struct reftable_stack *st = NULL;
-	int err;
-
-	err = reftable_new_stack(&st, dir, &opts);
-	check(!err);
-
-	reftable_addition_destroy(add);
-
-	err = reftable_stack_new_addition(&add, st, 0);
-	check(!err);
-
-	/*
-	 * write_limits_after_ref also updates the update indexes after adding
-	 * the record. This should cause an err to be returned, since the limits
-	 * must be set at the start.
-	 */
-	err = reftable_addition_add(add, write_limits_after_ref, &ref);
-	check_int(err, ==, REFTABLE_API_ERROR);
-
-	reftable_addition_destroy(add);
-	reftable_stack_destroy(st);
-	clear_dir(dir);
-}
-
-int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
-{
-	TEST(t_empty_add(), "empty addition to stack");
-	TEST(t_read_file(), "read_lines works");
-	TEST(t_reflog_expire(), "expire reflog entries");
-	TEST(t_reftable_invalid_limit_updates(), "prevent limit updates after adding records");
-	TEST(t_reftable_stack_add(), "add multiple refs and logs to stack");
-	TEST(t_reftable_stack_add_one(), "add a single ref record to stack");
-	TEST(t_reftable_stack_add_performs_auto_compaction(), "addition to stack triggers auto-compaction");
-	TEST(t_reftable_stack_auto_compaction(), "stack must form geometric sequence after compaction");
-	TEST(t_reftable_stack_auto_compaction_factor(), "auto-compaction with non-default geometric factor");
-	TEST(t_reftable_stack_auto_compaction_fails_gracefully(), "failure on auto-compaction");
-	TEST(t_reftable_stack_auto_compaction_with_locked_tables(), "auto compaction with locked tables");
-	TEST(t_reftable_stack_compaction_concurrent(), "compaction with concurrent stack");
-	TEST(t_reftable_stack_compaction_concurrent_clean(), "compaction with unclean stack shutdown");
-	TEST(t_reftable_stack_compaction_with_locked_tables(), "compaction with locked tables");
-	TEST(t_reftable_stack_hash_id(), "read stack with wrong hash ID");
-	TEST(t_reftable_stack_iterator(), "log and ref iterator for reftable stack");
-	TEST(t_reftable_stack_lock_failure(), "stack addition with lockfile failure");
-	TEST(t_reftable_stack_log_normalize(), "log messages should be normalized");
-	TEST(t_reftable_stack_read_across_reload(), "stack iterators work across reloads");
-	TEST(t_reftable_stack_reload_with_missing_table(), "stack iteration with garbage tables");
-	TEST(t_reftable_stack_tombstone(), "'tombstone' refs in stack");
-	TEST(t_reftable_stack_transaction_api(), "update transaction to stack");
-	TEST(t_reftable_stack_transaction_with_reload(), "transaction with reload");
-	TEST(t_reftable_stack_transaction_api_performs_auto_compaction(), "update transaction triggers auto-compaction");
-	TEST(t_reftable_stack_update_index_check(), "update transactions with equal update indices");
-	TEST(t_reftable_stack_uptodate(), "stack must be reloaded before ref update");
-	TEST(t_suggest_compaction_segment(), "suggest_compaction_segment with basic input");
-	TEST(t_suggest_compaction_segment_nothing(), "suggest_compaction_segment with pre-compacted input");
-
-	return test_done();
-}
diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c
new file mode 100644
index 0000000000..1f41d8c4ca
--- /dev/null
+++ b/t/unit-tests/u-reftable-stack.c
@@ -0,0 +1,1247 @@
+/*
+Copyright 2020 Google LLC
+
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file or at
+https://developers.google.com/open-source/licenses/bsd
+*/
+
+#define DISABLE_SIGN_COMPARE_WARNINGS
+
+#include "unit-test.h"
+#include "lib-reftable.h"
+#include "dir.h"
+#include "reftable/merged.h"
+#include "reftable/reader.h"
+#include "reftable/reftable-error.h"
+#include "reftable/stack.h"
+#include "strbuf.h"
+#include "tempfile.h"
+#include <dirent.h>
+
+static void clear_dir(const char *dirname)
+{
+	struct strbuf path = REFTABLE_BUF_INIT;
+	strbuf_addstr(&path, dirname);
+	remove_dir_recursively(&path, 0);
+	strbuf_release(&path);
+}
+
+static int count_dir_entries(const char *dirname)
+{
+	DIR *dir = opendir(dirname);
+	int len = 0;
+	struct dirent *d;
+	if (!dir)
+		return 0;
+
+	while ((d = readdir(dir))) {
+		/*
+		 * Besides skipping over "." and "..", we also need to
+		 * skip over other files that have a leading ".". This
+		 * is due to behaviour of NFS, which will rename files
+		 * to ".nfs*" to emulate delete-on-last-close.
+		 *
+		 * In any case this should be fine as the reftable
+		 * library will never write files with leading dots
+		 * anyway.
+		 */
+		if (starts_with(d->d_name, "."))
+			continue;
+		len++;
+	}
+	closedir(dir);
+	return len;
+}
+
+/*
+ * Work linenumber into the tempdir, so we can see which tests forget to
+ * cleanup.
+ */
+static char *get_tmp_template(int linenumber)
+{
+	const char *tmp = getenv("TMPDIR");
+	static char template[1024];
+	snprintf(template, sizeof(template) - 1, "%s/stack_test-%d.XXXXXX",
+		 tmp ? tmp : "/tmp", linenumber);
+	return template;
+}
+
+static char *get_tmp_dir(int linenumber)
+{
+	char *dir = get_tmp_template(linenumber);
+	cl_assert(mkdtemp(dir) != NULL);
+	return dir;
+}
+
+void test_reftable_stack__read_file(void)
+{
+	char *fn = get_tmp_template(__LINE__);
+	struct tempfile *tmp = mks_tempfile(fn);
+	int fd = get_tempfile_fd(tmp);
+	char out[1024] = "line1\n\nline2\nline3";
+	int n, err;
+	char **names = NULL;
+	const char *want[] = { "line1", "line2", "line3" };
+
+	cl_assert(fd > 0);
+	n = write_in_full(fd, out, strlen(out));
+	cl_assert_equal_i(n, strlen(out));
+	err = close(fd);
+	cl_assert(err >= 0);
+
+	err = read_lines(fn, &names);
+	cl_assert(err == 0);
+
+	for (size_t i = 0; names[i]; i++)
+		cl_assert_equal_s(want[i], names[i]);
+	free_names(names);
+	(void) remove(fn);
+	delete_tempfile(&tmp);
+}
+
+static int write_test_ref(struct reftable_writer *wr, void *arg)
+{
+	struct reftable_ref_record *ref = arg;
+	cl_assert(reftable_writer_set_limits(wr, ref->update_index,
+										 ref->update_index) == 0);
+	return reftable_writer_add_ref(wr, ref);
+}
+
+static void write_n_ref_tables(struct reftable_stack *st,
+			       size_t n)
+{
+	int disable_auto_compact;
+
+	disable_auto_compact = st->opts.disable_auto_compact;
+	st->opts.disable_auto_compact = 1;
+
+	for (size_t i = 0; i < n; i++) {
+		struct reftable_ref_record ref = {
+			.update_index = reftable_stack_next_update_index(st),
+			.value_type = REFTABLE_REF_VAL1,
+		};
+		char buf[128];
+
+		snprintf(buf, sizeof(buf), "refs/heads/branch-%04"PRIuMAX, (uintmax_t)i);
+		ref.refname = buf;
+		cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);
+
+		cl_assert(reftable_stack_add(st, &write_test_ref, &ref) == 0);
+	}
+
+	st->opts.disable_auto_compact = disable_auto_compact;
+}
+
+struct write_log_arg {
+	struct reftable_log_record *log;
+	uint64_t update_index;
+};
+
+static int write_test_log(struct reftable_writer *wr, void *arg)
+{
+	struct write_log_arg *wla = arg;
+
+	cl_assert(reftable_writer_set_limits(wr, wla->update_index,
+										 wla->update_index) == 0);
+	return reftable_writer_add_log(wr, wla->log);
+}
+
+void test_reftable_stack__add_one(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_buf scratch = REFTABLE_BUF_INIT;
+	int mask = umask(002);
+	struct reftable_write_options opts = {
+		.default_permissions = 0660,
+	};
+	struct reftable_stack *st = NULL;
+	struct reftable_ref_record ref = {
+		.refname = (char *) "HEAD",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+	struct reftable_ref_record dest = { 0 };
+	struct stat stat_result = { 0 };
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	cl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);
+	cl_assert(reftable_stack_read_ref(st, ref.refname, &dest) == 0);
+	cl_assert(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1) != 0);
+	cl_assert(st->readers_len > 0);
+
+#ifndef GIT_WINDOWS_NATIVE
+	cl_assert(reftable_buf_addstr(&scratch, dir) == 0);
+	cl_assert(reftable_buf_addstr(&scratch, "/tables.list") == 0);
+	cl_assert(stat(scratch.buf, &stat_result) == 0);
+	cl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);
+
+	reftable_buf_reset(&scratch);
+	cl_assert(reftable_buf_addstr(&scratch, dir) == 0);
+	cl_assert(reftable_buf_addstr(&scratch, "/") == 0);
+	/* do not try at home; not an external API for reftable. */
+	cl_assert(reftable_buf_addstr(&scratch, st->readers[0]->name) == 0);
+	cl_assert(stat(scratch.buf, &stat_result) == 0);
+	cl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);
+#else
+	(void) stat_result;
+#endif
+
+	reftable_ref_record_release(&dest);
+	reftable_stack_destroy(st);
+	reftable_buf_release(&scratch);
+	clear_dir(dir);
+	umask(mask);
+}
+
+void test_reftable_stack__uptodate(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st1 = NULL;
+	struct reftable_stack *st2 = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+
+	struct reftable_ref_record ref1 = {
+		.refname = (char *) "HEAD",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+	struct reftable_ref_record ref2 = {
+		.refname = (char *) "branch2",
+		.update_index = 2,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+
+
+	/* simulate multi-process access to the same stack
+	   by creating two stacks for the same directory.
+	 */
+	cl_assert(reftable_new_stack(&st1, dir, &opts) == 0);
+	cl_assert(reftable_new_stack(&st2, dir, &opts) == 0);
+	cl_assert(reftable_stack_add(st1, write_test_ref, &ref1) == 0);
+	cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, &ref2),
+					  REFTABLE_OUTDATED_ERROR);
+	cl_assert(reftable_stack_reload(st2) == 0);
+	cl_assert(reftable_stack_add(st2, write_test_ref, &ref2) == 0);
+	reftable_stack_destroy(st1);
+	reftable_stack_destroy(st2);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__transaction_api(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	struct reftable_addition *add = NULL;
+
+	struct reftable_ref_record ref = {
+		.refname = (char *) "HEAD",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+	struct reftable_ref_record dest = { 0 };
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	reftable_addition_destroy(add);
+
+	cl_assert(reftable_stack_new_addition(&add, st, 0) == 0);
+	cl_assert(reftable_addition_add(add, write_test_ref, &ref) == 0);
+	cl_assert(reftable_addition_commit(add) == 0);
+
+	reftable_addition_destroy(add);
+
+	cl_assert(reftable_stack_read_ref(st, ref.refname, &dest) == 0);
+	cl_assert_equal_i(REFTABLE_REF_SYMREF, dest.value_type);
+	cl_assert(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1) != 0);
+
+	reftable_ref_record_release(&dest);
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__transaction_with_reload(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_stack *st1 = NULL, *st2 = NULL;
+	struct reftable_addition *add = NULL;
+	struct reftable_ref_record refs[2] = {
+		{
+			.refname = (char *) "refs/heads/a",
+			.update_index = 1,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { '1' },
+		},
+		{
+			.refname = (char *) "refs/heads/b",
+			.update_index = 2,
+			.value_type = REFTABLE_REF_VAL1,
+			.value.val1 = { '1' },
+		},
+	};
+	struct reftable_ref_record ref = { 0 };
+
+	cl_assert(reftable_new_stack(&st1, dir, NULL) == 0);
+	cl_assert(reftable_new_stack(&st2, dir, NULL) == 0);
+	cl_assert(reftable_stack_new_addition(&add, st1, 0) == 0);
+	cl_assert(reftable_addition_add(add, write_test_ref, &refs[0]) == 0);
+	cl_assert(reftable_addition_commit(add) == 0);
+	reftable_addition_destroy(add);
+
+	/*
+	 * The second stack is now outdated, which we should notice. We do not
+	 * create the addition and lock the stack by default, but allow the
+	 * reload to happen when REFTABLE_STACK_NEW_ADDITION_RELOAD is set.
+	 */
+	cl_assert_equal_i(reftable_stack_new_addition(&add, st2, 0),
+					  REFTABLE_OUTDATED_ERROR);
+	cl_assert(reftable_stack_new_addition(&add, st2,
+										  REFTABLE_STACK_NEW_ADDITION_RELOAD) == 0);
+	cl_assert(reftable_addition_add(add, write_test_ref, &refs[1]) == 0);
+	cl_assert(reftable_addition_commit(add) == 0);
+	reftable_addition_destroy(add);
+
+	for (size_t i = 0; i < ARRAY_SIZE(refs); i++) {
+		cl_assert(reftable_stack_read_ref(st2, refs[i].refname, &ref) == 0);
+		cl_assert(reftable_ref_record_equal(&refs[i], &ref,
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+	}
+
+	reftable_ref_record_release(&ref);
+	reftable_stack_destroy(st1);
+	reftable_stack_destroy(st2);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__transaction_api_performs_auto_compaction(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = {0};
+	struct reftable_addition *add = NULL;
+	struct reftable_stack *st = NULL;
+	size_t n = 20;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (size_t i = 0; i <= n; i++) {
+		struct reftable_ref_record ref = {
+			.update_index = reftable_stack_next_update_index(st),
+			.value_type = REFTABLE_REF_SYMREF,
+			.value.symref = (char *) "master",
+		};
+		char name[100];
+
+		snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
+		ref.refname = name;
+
+		/*
+		 * Disable auto-compaction for all but the last runs. Like this
+		 * we can ensure that we indeed honor this setting and have
+		 * better control over when exactly auto compaction runs.
+		 */
+		st->opts.disable_auto_compact = i != n;
+
+		cl_assert(reftable_stack_new_addition(&add, st, 0) == 0);
+		cl_assert(reftable_addition_add(add, write_test_ref, &ref) == 0);
+		cl_assert(reftable_addition_commit(add) == 0);
+
+		reftable_addition_destroy(add);
+
+		/*
+		 * The stack length should grow continuously for all runs where
+		 * auto compaction is disabled. When enabled, we should merge
+		 * all tables in the stack.
+		 */
+		if (i != n)
+			cl_assert_equal_i(st->merged->readers_len, i + 1);
+		else
+			cl_assert_equal_i(st->merged->readers_len, 1);
+	}
+
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__auto_compaction_fails_gracefully(void)
+{
+	struct reftable_ref_record ref = {
+		.refname = (char *) "refs/heads/master",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_VAL1,
+		.value.val1 = {0x01},
+	};
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st;
+	struct reftable_buf table_path = REFTABLE_BUF_INIT;
+	char *dir = get_tmp_dir(__LINE__);
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	cl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);
+	cl_assert_equal_i(st->merged->readers_len, 1);
+	cl_assert_equal_i(st->stats.attempts, 0);
+	cl_assert_equal_i(st->stats.failures, 0);
+
+	/*
+	 * Lock the newly written table such that it cannot be compacted.
+	 * Adding a new table to the stack should not be impacted by this, even
+	 * though auto-compaction will now fail.
+	 */
+	cl_assert(reftable_buf_addstr(&table_path, dir) == 0);
+	cl_assert(reftable_buf_addstr(&table_path, "/") == 0);
+	cl_assert(reftable_buf_addstr(&table_path, st->readers[0]->name) == 0);
+	cl_assert(reftable_buf_addstr(&table_path, ".lock") == 0);
+	write_file_buf(table_path.buf, "", 0);
+
+	ref.update_index = 2;
+	cl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);
+	cl_assert_equal_i(st->merged->readers_len, 2);
+	cl_assert_equal_i(st->stats.attempts, 1);
+	cl_assert_equal_i(st->stats.failures, 1);
+
+	reftable_stack_destroy(st);
+	reftable_buf_release(&table_path);
+	clear_dir(dir);
+}
+
+static int write_error(struct reftable_writer *wr UNUSED, void *arg)
+{
+	return *((int *)arg);
+}
+
+void test_reftable_stack__update_index_check(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	struct reftable_ref_record ref1 = {
+		.refname = (char *) "name1",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+	struct reftable_ref_record ref2 = {
+		.refname = (char *) "name2",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	cl_assert(reftable_stack_add(st, write_test_ref, &ref1) == 0);
+	cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+										 &ref2), REFTABLE_API_ERROR);
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__lock_failure(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	int i;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
+		cl_assert_equal_i(reftable_stack_add(st, write_error, &i), i);
+
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__add(void)
+{
+	struct reftable_write_options opts = {
+		.exact_log_message = 1,
+		.default_permissions = 0660,
+		.disable_auto_compact = 1,
+	};
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_ref_record refs[2] = { 0 };
+	struct reftable_log_record logs[2] = { 0 };
+	struct reftable_buf path = REFTABLE_BUF_INIT;
+	struct stat stat_result;
+	size_t i, N = ARRAY_SIZE(refs);
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (i = 0; i < N; i++) {
+		char buf[256];
+		snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
+		refs[i].refname = xstrdup(buf);
+		refs[i].update_index = i + 1;
+		refs[i].value_type = REFTABLE_REF_VAL1;
+		cl_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);
+
+		logs[i].refname = xstrdup(buf);
+		logs[i].update_index = N + i + 1;
+		logs[i].value_type = REFTABLE_LOG_UPDATE;
+		logs[i].value.update.email = xstrdup("identity@invalid");
+		cl_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);
+	}
+
+	for (i = 0; i < N; i++)
+		cl_assert(reftable_stack_add(st, write_test_ref, &refs[i]) == 0);
+
+	for (i = 0; i < N; i++) {
+		struct write_log_arg arg = {
+			.log = &logs[i],
+			.update_index = reftable_stack_next_update_index(st),
+		};
+		cl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);
+	}
+
+	cl_assert(reftable_stack_compact_all(st, NULL) == 0);
+
+	for (i = 0; i < N; i++) {
+		struct reftable_ref_record dest = { 0 };
+
+		cl_assert(reftable_stack_read_ref(st, refs[i].refname, &dest) == 0);
+		cl_assert(reftable_ref_record_equal(&dest, refs + i,
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_ref_record_release(&dest);
+	}
+
+	for (i = 0; i < N; i++) {
+		struct reftable_log_record dest = { 0 };
+		cl_assert(reftable_stack_read_log(st, refs[i].refname, &dest) == 0);
+		cl_assert(reftable_log_record_equal(&dest, logs + i,
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_log_record_release(&dest);
+	}
+
+#ifndef GIT_WINDOWS_NATIVE
+	cl_assert(reftable_buf_addstr(&path, dir) == 0);
+	cl_assert(reftable_buf_addstr(&path, "/tables.list") == 0);
+	cl_assert(stat(path.buf, &stat_result) == 0);
+	cl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);
+
+	reftable_buf_reset(&path);
+	cl_assert(reftable_buf_addstr(&path, dir) == 0);
+	cl_assert(reftable_buf_addstr(&path, "/") == 0);
+	/* do not try at home; not an external API for reftable. */
+	cl_assert(reftable_buf_addstr(&path, st->readers[0]->name) == 0);
+	cl_assert(stat(path.buf, &stat_result) == 0);
+	cl_assert_equal_i((stat_result.st_mode & 0777), opts.default_permissions);
+#else
+	(void) stat_result;
+#endif
+
+	/* cleanup */
+	reftable_stack_destroy(st);
+	for (i = 0; i < N; i++) {
+		reftable_ref_record_release(&refs[i]);
+		reftable_log_record_release(&logs[i]);
+	}
+	reftable_buf_release(&path);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__iterator(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_ref_record refs[10] = { 0 };
+	struct reftable_log_record logs[10] = { 0 };
+	struct reftable_iterator it = { 0 };
+	size_t N = ARRAY_SIZE(refs), i;
+	int err;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (i = 0; i < N; i++) {
+		refs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
+		refs[i].update_index = i + 1;
+		refs[i].value_type = REFTABLE_REF_VAL1;
+		cl_reftable_set_hash(refs[i].value.val1, i, REFTABLE_HASH_SHA1);
+
+		logs[i].refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
+		logs[i].update_index = i + 1;
+		logs[i].value_type = REFTABLE_LOG_UPDATE;
+		logs[i].value.update.email = xstrdup("johndoe@invalid");
+		logs[i].value.update.message = xstrdup("commit\n");
+		cl_reftable_set_hash(logs[i].value.update.new_hash, i, REFTABLE_HASH_SHA1);
+	}
+
+	for (i = 0; i < N; i++)
+		cl_assert(reftable_stack_add(st, write_test_ref, &refs[i]) == 0);
+
+	for (i = 0; i < N; i++) {
+		struct write_log_arg arg = {
+			.log = &logs[i],
+			.update_index = reftable_stack_next_update_index(st),
+		};
+
+		cl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);
+	}
+
+	reftable_stack_init_ref_iterator(st, &it);
+	reftable_iterator_seek_ref(&it, refs[0].refname);
+	for (i = 0; ; i++) {
+		struct reftable_ref_record ref = { 0 };
+
+		err = reftable_iterator_next_ref(&it, &ref);
+		if (err > 0)
+			break;
+		cl_assert(err == 0);
+		cl_assert(reftable_ref_record_equal(&ref, &refs[i],
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_ref_record_release(&ref);
+	}
+	cl_assert_equal_i(i, N);
+
+	reftable_iterator_destroy(&it);
+
+	cl_assert(reftable_stack_init_log_iterator(st, &it) == 0);
+
+	reftable_iterator_seek_log(&it, logs[0].refname);
+	for (i = 0; ; i++) {
+		struct reftable_log_record log = { 0 };
+
+		err = reftable_iterator_next_log(&it, &log);
+		if (err > 0)
+			break;
+		cl_assert(err == 0);
+		cl_assert(reftable_log_record_equal(&log, &logs[i],
+											REFTABLE_HASH_SIZE_SHA1) != 0);
+		reftable_log_record_release(&log);
+	}
+	cl_assert_equal_i(i, N);
+
+	reftable_stack_destroy(st);
+	reftable_iterator_destroy(&it);
+	for (i = 0; i < N; i++) {
+		reftable_ref_record_release(&refs[i]);
+		reftable_log_record_release(&logs[i]);
+	}
+	clear_dir(dir);
+}
+
+void test_reftable_stack__log_normalize(void)
+{
+	struct reftable_write_options opts = {
+		0,
+	};
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_log_record input = {
+		.refname = (char *) "branch",
+		.update_index = 1,
+		.value_type = REFTABLE_LOG_UPDATE,
+		.value = {
+			.update = {
+				.new_hash = { 1 },
+				.old_hash = { 2 },
+			},
+		},
+	};
+	struct reftable_log_record dest = {
+		.update_index = 0,
+	};
+	struct write_log_arg arg = {
+		.log = &input,
+		.update_index = 1,
+	};
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	input.value.update.message = (char *) "one\ntwo";
+	cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+										 &arg), REFTABLE_API_ERROR);
+
+	input.value.update.message = (char *) "one";
+	cl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);
+	cl_assert(reftable_stack_read_log(st, input.refname, &dest) == 0);
+	cl_assert_equal_s(dest.value.update.message, "one\n");
+
+	input.value.update.message = (char *) "two\n";
+	arg.update_index = 2;
+	cl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);
+	cl_assert(reftable_stack_read_log(st, input.refname, &dest) == 0);
+	cl_assert_equal_s(dest.value.update.message, "two\n");
+
+	/* cleanup */
+	reftable_stack_destroy(st);
+	reftable_log_record_release(&dest);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__tombstone(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	struct reftable_ref_record refs[2] = { 0 };
+	struct reftable_log_record logs[2] = { 0 };
+	size_t i, N = ARRAY_SIZE(refs);
+	struct reftable_ref_record dest = { 0 };
+	struct reftable_log_record log_dest = { 0 };
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	/* even entries add the refs, odd entries delete them. */
+	for (i = 0; i < N; i++) {
+		const char *buf = "branch";
+		refs[i].refname = xstrdup(buf);
+		refs[i].update_index = i + 1;
+		if (i % 2 == 0) {
+			refs[i].value_type = REFTABLE_REF_VAL1;
+			cl_reftable_set_hash(refs[i].value.val1, i,
+					    REFTABLE_HASH_SHA1);
+		}
+
+		logs[i].refname = xstrdup(buf);
+		/*
+		 * update_index is part of the key so should be constant.
+		 * The value itself should be less than the writer's upper
+		 * limit.
+		 */
+		logs[i].update_index = 1;
+		if (i % 2 == 0) {
+			logs[i].value_type = REFTABLE_LOG_UPDATE;
+			cl_reftable_set_hash(logs[i].value.update.new_hash, i,
+					    REFTABLE_HASH_SHA1);
+			logs[i].value.update.email =
+				xstrdup("identity@invalid");
+		}
+	}
+	for (i = 0; i < N; i++)
+		cl_assert(reftable_stack_add(st, write_test_ref, &refs[i]) == 0);
+
+	for (i = 0; i < N; i++) {
+		struct write_log_arg arg = {
+			.log = &logs[i],
+			.update_index = reftable_stack_next_update_index(st),
+		};
+		cl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);
+	}
+
+	cl_assert_equal_i(reftable_stack_read_ref(st, "branch", &dest), 1);
+	reftable_ref_record_release(&dest);
+
+	cl_assert_equal_i(reftable_stack_read_log(st, "branch", &log_dest), 1);
+	reftable_log_record_release(&log_dest);
+
+	cl_assert(reftable_stack_compact_all(st, NULL) == 0);
+	cl_assert_equal_i(reftable_stack_read_ref(st, "branch", &dest), 1);
+	cl_assert_equal_i(reftable_stack_read_log(st, "branch", &log_dest), 1);
+	reftable_ref_record_release(&dest);
+	reftable_log_record_release(&log_dest);
+
+	/* cleanup */
+	reftable_stack_destroy(st);
+	for (i = 0; i < N; i++) {
+		reftable_ref_record_release(&refs[i]);
+		reftable_log_record_release(&logs[i]);
+	}
+	clear_dir(dir);
+}
+
+void test_reftable_stack__hash_id(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+
+	struct reftable_ref_record ref = {
+		.refname = (char *) "master",
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "target",
+		.update_index = 1,
+	};
+	struct reftable_write_options opts32 = { .hash_id = REFTABLE_HASH_SHA256 };
+	struct reftable_stack *st32 = NULL;
+	struct reftable_write_options opts_default = { 0 };
+	struct reftable_stack *st_default = NULL;
+	struct reftable_ref_record dest = { 0 };
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	cl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);
+
+	/* can't read it with the wrong hash ID. */
+	cl_assert_equal_i(reftable_new_stack(&st32, dir, &opts32), REFTABLE_FORMAT_ERROR);
+
+	/* check that we can read it back with default opts too. */
+	cl_assert(reftable_new_stack(&st_default, dir, &opts_default) == 0);
+	cl_assert(reftable_stack_read_ref(st_default, "master", &dest) == 0);
+	cl_assert(reftable_ref_record_equal(&ref, &dest, REFTABLE_HASH_SIZE_SHA1) != 0);
+	reftable_ref_record_release(&dest);
+	reftable_stack_destroy(st);
+	reftable_stack_destroy(st_default);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__suggest_compaction_segment(void)
+{
+	uint64_t sizes[] = { 512, 64, 17, 16, 9, 9, 9, 16, 2, 16 };
+	struct segment min =
+		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
+	cl_assert_equal_i(min.start, 1);
+	cl_assert_equal_i(min.end, 10);
+}
+
+void test_reftable_stack__suggest_compaction_segment_nothing(void)
+{
+	uint64_t sizes[] = { 64, 32, 16, 8, 4, 2 };
+	struct segment result =
+		suggest_compaction_segment(sizes, ARRAY_SIZE(sizes), 2);
+	cl_assert_equal_i(result.start, result.end);
+}
+
+void test_reftable_stack__reflog_expire(void)
+{
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	struct reftable_log_record logs[20] = { 0 };
+	size_t i, N = ARRAY_SIZE(logs) - 1;
+	struct reftable_log_expiry_config expiry = {
+		.time = 10,
+	};
+	struct reftable_log_record log = { 0 };
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (i = 1; i <= N; i++) {
+		char buf[256];
+		snprintf(buf, sizeof(buf), "branch%02"PRIuMAX, (uintmax_t)i);
+
+		logs[i].refname = xstrdup(buf);
+		logs[i].update_index = i;
+		logs[i].value_type = REFTABLE_LOG_UPDATE;
+		logs[i].value.update.time = i;
+		logs[i].value.update.email = xstrdup("identity@invalid");
+		cl_reftable_set_hash(logs[i].value.update.new_hash, i,
+				    REFTABLE_HASH_SHA1);
+	}
+
+	for (i = 1; i <= N; i++) {
+		struct write_log_arg arg = {
+			.log = &logs[i],
+			.update_index = reftable_stack_next_update_index(st),
+		};
+		cl_assert(reftable_stack_add(st, write_test_log, &arg) == 0);
+	}
+
+	cl_assert(reftable_stack_compact_all(st, NULL) == 0);
+	cl_assert(reftable_stack_compact_all(st, &expiry) == 0);
+	cl_assert_equal_i(reftable_stack_read_log(st, logs[9].refname, &log), 1);
+	cl_assert(reftable_stack_read_log(st, logs[11].refname, &log) == 0);
+
+	expiry.min_update_index = 15;
+	cl_assert(reftable_stack_compact_all(st, &expiry) == 0);
+	cl_assert_equal_i(reftable_stack_read_log(st, logs[14].refname, &log), 1);
+	cl_assert(reftable_stack_read_log(st, logs[16].refname, &log) == 0);
+
+	/* cleanup */
+	reftable_stack_destroy(st);
+	for (i = 0; i <= N; i++)
+		reftable_log_record_release(&logs[i]);
+	clear_dir(dir);
+	reftable_log_record_release(&log);
+}
+
+static int write_nothing(struct reftable_writer *wr, void *arg UNUSED)
+{
+	cl_assert(reftable_writer_set_limits(wr, 1, 1) == 0);
+	return 0;
+}
+
+void test_reftable_stack__empty_add(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_stack *st2 = NULL;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	cl_assert(reftable_stack_add(st, write_nothing, NULL) == 0);
+	cl_assert(reftable_new_stack(&st2, dir, &opts) == 0);
+	clear_dir(dir);
+	reftable_stack_destroy(st);
+	reftable_stack_destroy(st2);
+}
+
+static int fastlogN(uint64_t sz, uint64_t N)
+{
+	int l = 0;
+	if (sz == 0)
+		return 0;
+	for (; sz; sz /= N)
+		l++;
+	return l - 1;
+}
+
+void test_reftable_stack__auto_compaction(void)
+{
+	struct reftable_write_options opts = {
+		.disable_auto_compact = 1,
+	};
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	size_t i, N = 100;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (i = 0; i < N; i++) {
+		char name[100];
+		struct reftable_ref_record ref = {
+			.refname = name,
+			.update_index = reftable_stack_next_update_index(st),
+			.value_type = REFTABLE_REF_SYMREF,
+			.value.symref = (char *) "master",
+		};
+		snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
+
+		cl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);
+		cl_assert(reftable_stack_auto_compact(st) == 0);
+		cl_assert(i < 2 || st->merged->readers_len < 2 * fastlogN(i, 2));
+	}
+
+	cl_assert(reftable_stack_compaction_stats(st)->entries_written <
+			  (uint64_t)(N * fastlogN(N, 2)));
+
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__auto_compaction_factor(void)
+{
+	struct reftable_write_options opts = {
+		.auto_compaction_factor = 5,
+	};
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	size_t N = 100;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (size_t i = 0; i < N; i++) {
+		char name[20];
+		struct reftable_ref_record ref = {
+			.refname = name,
+			.update_index = reftable_stack_next_update_index(st),
+			.value_type = REFTABLE_REF_VAL1,
+		};
+		xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
+
+		cl_assert(reftable_stack_add(st, &write_test_ref, &ref) == 0);
+		cl_assert(i < 5 || st->merged->readers_len < 5 * fastlogN(i, 5));
+	}
+
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__auto_compaction_with_locked_tables(void)
+{
+	struct reftable_write_options opts = {
+		.disable_auto_compact = 1,
+	};
+	struct reftable_stack *st = NULL;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	char *dir = get_tmp_dir(__LINE__);
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	write_n_ref_tables(st, 5);
+	cl_assert_equal_i(st->merged->readers_len, 5);
+
+	/*
+	 * Given that all tables we have written should be roughly the same
+	 * size, we expect that auto-compaction will want to compact all of the
+	 * tables. Locking any of the tables will keep it from doing so.
+	 */
+	cl_assert(reftable_buf_addstr(&buf, dir) == 0);
+	cl_assert(reftable_buf_addstr(&buf, "/") == 0);
+	cl_assert(reftable_buf_addstr(&buf, st->readers[2]->name) == 0);
+	cl_assert(reftable_buf_addstr(&buf, ".lock") == 0);
+	write_file_buf(buf.buf, "", 0);
+
+	/*
+	 * When parts of the stack are locked, then auto-compaction does a best
+	 * effort compaction of those tables which aren't locked. So while this
+	 * would in theory compact all tables, due to the preexisting lock we
+	 * only compact the newest two tables.
+	 */
+	cl_assert(reftable_stack_auto_compact(st) == 0);
+	cl_assert_equal_i(st->stats.failures, 0);
+	cl_assert_equal_i(st->merged->readers_len, 4);
+
+	reftable_stack_destroy(st);
+	reftable_buf_release(&buf);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__add_performs_auto_compaction(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	size_t i, n = 20;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	for (i = 0; i <= n; i++) {
+		struct reftable_ref_record ref = {
+			.update_index = reftable_stack_next_update_index(st),
+			.value_type = REFTABLE_REF_SYMREF,
+			.value.symref = (char *) "master",
+		};
+		char buf[128];
+
+		/*
+		 * Disable auto-compaction for all but the last runs. Like this
+		 * we can ensure that we indeed honor this setting and have
+		 * better control over when exactly auto compaction runs.
+		 */
+		st->opts.disable_auto_compact = i != n;
+
+		snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i);
+		ref.refname = buf;
+
+		cl_assert(reftable_stack_add(st, write_test_ref, &ref) == 0);
+
+		/*
+		 * The stack length should grow continuously for all runs where
+		 * auto compaction is disabled. When enabled, we should merge
+		 * all tables in the stack.
+		 */
+		if (i != n)
+			cl_assert_equal_i(st->merged->readers_len, i + 1);
+		else
+			cl_assert_equal_i(st->merged->readers_len, 1);
+	}
+
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__compaction_with_locked_tables(void)
+{
+	struct reftable_write_options opts = {
+		.disable_auto_compact = 1,
+	};
+	struct reftable_stack *st = NULL;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	char *dir = get_tmp_dir(__LINE__);
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	write_n_ref_tables(st, 3);
+	cl_assert_equal_i(st->merged->readers_len, 3);
+
+	/* Lock one of the tables that we're about to compact. */
+	cl_assert(reftable_buf_addstr(&buf, dir) == 0);
+	cl_assert(reftable_buf_addstr(&buf, "/") == 0);
+	cl_assert(reftable_buf_addstr(&buf, st->readers[1]->name) == 0);
+	cl_assert(reftable_buf_addstr(&buf, ".lock") == 0);
+	write_file_buf(buf.buf, "", 0);
+
+	/*
+	 * Compaction is expected to fail given that we were not able to
+	 * compact all tables.
+	 */
+	cl_assert_equal_i(reftable_stack_compact_all(st, NULL), REFTABLE_LOCK_ERROR);
+	cl_assert_equal_i(st->stats.failures, 1);
+	cl_assert_equal_i(st->merged->readers_len, 3);
+
+	reftable_stack_destroy(st);
+	reftable_buf_release(&buf);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__compaction_concurrent(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st1 = NULL, *st2 = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+
+	cl_assert(reftable_new_stack(&st1, dir, &opts) == 0);
+	write_n_ref_tables(st1, 3);
+
+	cl_assert(reftable_new_stack(&st2, dir, &opts) == 0);
+	cl_assert(reftable_stack_compact_all(st1, NULL) == 0);
+
+	reftable_stack_destroy(st1);
+	reftable_stack_destroy(st2);
+
+	cl_assert_equal_i(count_dir_entries(dir), 2);
+	clear_dir(dir);
+}
+
+static void unclean_stack_close(struct reftable_stack *st)
+{
+	/* break abstraction boundary to simulate unclean shutdown. */
+	for (size_t i = 0; i < st->readers_len; i++)
+		reftable_reader_decref(st->readers[i]);
+	st->readers_len = 0;
+	REFTABLE_FREE_AND_NULL(st->readers);
+}
+
+void test_reftable_stack__compaction_concurrent_clean(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st1 = NULL, *st2 = NULL, *st3 = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+
+	cl_assert(reftable_new_stack(&st1, dir, &opts) == 0);
+	write_n_ref_tables(st1, 3);
+
+	cl_assert(reftable_new_stack(&st2, dir, &opts) == 0);
+	cl_assert(reftable_stack_compact_all(st1, NULL) == 0);
+
+	unclean_stack_close(st1);
+	unclean_stack_close(st2);
+
+	cl_assert(reftable_new_stack(&st3, dir, &opts) == 0);
+	cl_assert(reftable_stack_clean(st3) == 0);
+	cl_assert_equal_i(count_dir_entries(dir), 2);
+
+	reftable_stack_destroy(st1);
+	reftable_stack_destroy(st2);
+	reftable_stack_destroy(st3);
+
+	clear_dir(dir);
+}
+
+void test_reftable_stack__read_across_reload(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st1 = NULL, *st2 = NULL;
+	struct reftable_ref_record rec = { 0 };
+	struct reftable_iterator it = { 0 };
+	char *dir = get_tmp_dir(__LINE__);
+
+	/* Create a first stack and set up an iterator for it. */
+	cl_assert(reftable_new_stack(&st1, dir, &opts) == 0);
+	write_n_ref_tables(st1, 2);
+	cl_assert_equal_i(st1->merged->readers_len, 2);
+	reftable_stack_init_ref_iterator(st1, &it);
+	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+
+	/* Set up a second stack for the same directory and compact it. */
+	cl_assert(reftable_new_stack(&st2, dir, &opts) == 0);
+	cl_assert_equal_i(st2->merged->readers_len, 2);
+	cl_assert(reftable_stack_compact_all(st2, NULL) == 0);
+	cl_assert_equal_i(st2->merged->readers_len, 1);
+
+	/*
+	 * Verify that we can continue to use the old iterator even after we
+	 * have reloaded its stack.
+	 */
+	cl_assert(reftable_stack_reload(st1) == 0);
+	cl_assert_equal_i(st1->merged->readers_len, 1);
+	cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+	cl_assert_equal_s(rec.refname, "refs/heads/branch-0000");
+	cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+	cl_assert_equal_s(rec.refname, "refs/heads/branch-0001");
+	cl_assert(reftable_iterator_next_ref(&it, &rec) > 0);
+
+	reftable_ref_record_release(&rec);
+	reftable_iterator_destroy(&it);
+	reftable_stack_destroy(st1);
+	reftable_stack_destroy(st2);
+	clear_dir(dir);
+}
+
+void test_reftable_stack__reload_with_missing_table(void)
+{
+	struct reftable_write_options opts = { 0 };
+	struct reftable_stack *st = NULL;
+	struct reftable_ref_record rec = { 0 };
+	struct reftable_iterator it = { 0 };
+	struct reftable_buf table_path = REFTABLE_BUF_INIT, content = REFTABLE_BUF_INIT;
+	char *dir = get_tmp_dir(__LINE__);
+
+	/* Create a first stack and set up an iterator for it. */
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+	write_n_ref_tables(st, 2);
+	cl_assert_equal_i(st->merged->readers_len, 2);
+	reftable_stack_init_ref_iterator(st, &it);
+	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
+
+	/*
+	 * Update the tables.list file with some garbage data, while reusing
+	 * our old readers. This should trigger a partial reload of the stack,
+	 * where we try to reuse our old readers.
+	*/
+	cl_assert(reftable_buf_addstr(&content, st->readers[0]->name) == 0);
+	cl_assert(reftable_buf_addstr(&content, "\n") == 0);
+	cl_assert(reftable_buf_addstr(&content, st->readers[1]->name) == 0);
+	cl_assert(reftable_buf_addstr(&content, "\n") == 0);
+	cl_assert(reftable_buf_addstr(&content, "garbage\n") == 0);
+	cl_assert(reftable_buf_addstr(&table_path, st->list_file) == 0);
+	cl_assert(reftable_buf_addstr(&table_path, ".lock") == 0);
+	write_file_buf(table_path.buf, content.buf, content.len);
+	cl_assert(rename(table_path.buf, st->list_file) == 0);
+
+	cl_assert_equal_i(reftable_stack_reload(st), -4);
+	cl_assert_equal_i(st->merged->readers_len, 2);
+
+	/*
+	 * Even though the reload has failed, we should be able to continue
+	 * using the iterator.
+	*/
+	cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+	cl_assert_equal_s(rec.refname, "refs/heads/branch-0000");
+	cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
+	cl_assert_equal_s(rec.refname, "refs/heads/branch-0001");
+	cl_assert(reftable_iterator_next_ref(&it, &rec) > 0);
+
+	reftable_ref_record_release(&rec);
+	reftable_iterator_destroy(&it);
+	reftable_stack_destroy(st);
+	reftable_buf_release(&table_path);
+	reftable_buf_release(&content);
+	clear_dir(dir);
+}
+
+static int write_limits_after_ref(struct reftable_writer *wr, void *arg)
+{
+	struct reftable_ref_record *ref = arg;
+	cl_assert(reftable_writer_set_limits(wr, ref->update_index,
+										 ref->update_index) == 0);
+	cl_assert(reftable_writer_add_ref(wr, ref) == 0);
+	return reftable_writer_set_limits(wr, ref->update_index, ref->update_index);
+}
+
+void test_reftable_stack__invalid_limit_updates(void)
+{
+	struct reftable_ref_record ref = {
+		.refname = (char *) "HEAD",
+		.update_index = 1,
+		.value_type = REFTABLE_REF_SYMREF,
+		.value.symref = (char *) "master",
+	};
+	struct reftable_write_options opts = {
+		.default_permissions = 0660,
+	};
+	struct reftable_addition *add = NULL;
+	char *dir = get_tmp_dir(__LINE__);
+	struct reftable_stack *st = NULL;
+
+	cl_assert(reftable_new_stack(&st, dir, &opts) == 0);
+
+	reftable_addition_destroy(add);
+
+	cl_assert(reftable_stack_new_addition(&add, st, 0) == 0);
+
+	/*
+	 * write_limits_after_ref also updates the update indexes after adding
+	 * the record. This should cause an err to be returned, since the limits
+	 * must be set at the start.
+	 */
+	cl_assert_equal_i(reftable_addition_add(add, write_limits_after_ref, &ref),
+					  REFTABLE_API_ERROR);
+
+	reftable_addition_destroy(add);
+	reftable_stack_destroy(st);
+	clear_dir(dir);
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
  2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
                   ` (8 preceding siblings ...)
  2025-04-29 17:53 ` [PATCH v2 09/10] t/unit-tests: convert reftable stack " Seyi Kuforiji
@ 2025-04-29 17:53 ` Seyi Kuforiji
  2025-05-02  9:57   ` Patrick Steinhardt
  9 siblings, 1 reply; 31+ messages in thread
From: Seyi Kuforiji @ 2025-04-29 17:53 UTC (permalink / raw)
  To: git; +Cc: ps, phillip.wood, Seyi Kuforiji

Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are
required for the reftable-related test files to run efficeintly. In the
current implementation these functions are designed to conform with our
homegrown unit-testing structure. So in other to convert the reftable
test files, there is need for a clar specific implementation of these
helper functions.

type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++)`
Adapt functions in lib-reftable.{c,h} to use clar. These functions
conform with the clar testing framework and become available for all
reftable-related test files implemented using the clar testing
framework, which requires them. This change migrates the helper
functions back into `lib-reftable.{c,h}`.

Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
---
 Makefile                    |  4 +-
 t/meson.build               |  4 +-
 t/unit-tests/lib-reftable.c | 26 +++++------
 t/unit-tests/lib-reftable.h |  6 +--
 t/unit-tests/unit-test.c    | 93 -------------------------------------
 t/unit-tests/unit-test.h    | 16 -------
 6 files changed, 20 insertions(+), 129 deletions(-)

diff --git a/Makefile b/Makefile
index 0b42893611..7e646e16ee 100644
--- a/Makefile
+++ b/Makefile
@@ -1379,12 +1379,12 @@ CLAR_TEST_SUITES += u-urlmatch-normalization
 CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
 CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES))
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
-CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
+CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
+CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
 
 UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
 UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
-UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
 
 # xdiff and reftable libs may in turn depend on what is in libgit.a
 GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
diff --git a/t/meson.build b/t/meson.build
index 8fa00fc9ef..7c305a90b5 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -26,8 +26,9 @@ clar_test_suites = [
 
 clar_sources = [
   'unit-tests/clar/clar.c',
+  'unit-tests/lib-oid.c',
+  'unit-tests/lib-reftable.c',
   'unit-tests/unit-test.c',
-  'unit-tests/lib-oid.c'
 ]
 
 clar_decls_h = custom_target(
@@ -69,7 +70,6 @@ foreach unit_test_program : unit_test_programs
   unit_test = executable(unit_test_name,
     sources: [
       'unit-tests/test-lib.c',
-      'unit-tests/lib-reftable.c',
       unit_test_program,
     ],
     dependencies: [libgit_commonmain],
diff --git a/t/unit-tests/lib-reftable.c b/t/unit-tests/lib-reftable.c
index 8a69612266..414364166f 100644
--- a/t/unit-tests/lib-reftable.c
+++ b/t/unit-tests/lib-reftable.c
@@ -1,12 +1,12 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "lib-reftable.h"
-#include "test-lib.h"
+#include "unit-test.h"
 #include "reftable/constants.h"
 #include "reftable/writer.h"
 #include "strbuf.h"
 
-void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)
+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)
 {
 	memset(p, (uint8_t)i, hash_size(id));
 }
@@ -22,17 +22,17 @@ static int strbuf_writer_flush(void *arg UNUSED)
 	return 0;
 }
 
-struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
+struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
 						 struct reftable_write_options *opts)
 {
 	struct reftable_writer *writer;
 	int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,
 				      buf, opts);
-	check(!ret);
+	cl_assert(ret == 0);
 	return writer;
 }
 
-void t_reftable_write_to_buf(struct reftable_buf *buf,
+void cl_reftable_write_to_buf(struct reftable_buf *buf,
 			     struct reftable_ref_record *refs,
 			     size_t nrefs,
 			     struct reftable_log_record *logs,
@@ -64,35 +64,35 @@ void t_reftable_write_to_buf(struct reftable_buf *buf,
 			min = ui;
 	}
 
-	writer = t_reftable_strbuf_writer(buf, &opts);
+	writer = cl_reftable_strbuf_writer(buf, &opts);
 	reftable_writer_set_limits(writer, min, max);
 
 	if (nrefs) {
 		ret = reftable_writer_add_refs(writer, refs, nrefs);
-		check_int(ret, ==, 0);
+		cl_assert_equal_i(ret, 0);
 	}
 
 	if (nlogs) {
 		ret = reftable_writer_add_logs(writer, logs, nlogs);
-		check_int(ret, ==, 0);
+		cl_assert_equal_i(ret, 0);
 	}
 
 	ret = reftable_writer_close(writer);
-	check_int(ret, ==, 0);
+	cl_assert_equal_i(ret, 0);
 
 	stats = reftable_writer_stats(writer);
-	for (size_t i = 0; i < stats->ref_stats.blocks; i++) {
+	for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++) {
 		size_t off = i * (opts.block_size ? opts.block_size
 						  : DEFAULT_BLOCK_SIZE);
 		if (!off)
 			off = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);
-		check_char(buf->buf[off], ==, 'r');
+		cl_assert(buf->buf[off] == 'r');
 	}
 
 	if (nrefs)
-		check_int(stats->ref_stats.blocks, >, 0);
+		cl_assert(stats->ref_stats.blocks > 0);
 	if (nlogs)
-		check_int(stats->log_stats.blocks, >, 0);
+		cl_assert(stats->log_stats.blocks > 0);
 
 	reftable_writer_free(writer);
 }
diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
index e4c360fa7e..2958db5dc0 100644
--- a/t/unit-tests/lib-reftable.h
+++ b/t/unit-tests/lib-reftable.h
@@ -6,12 +6,12 @@
 
 struct reftable_buf;
 
-void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
+void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
 
-struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
+struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
 						 struct reftable_write_options *opts);
 
-void t_reftable_write_to_buf(struct reftable_buf *buf,
+void cl_reftable_write_to_buf(struct reftable_buf *buf,
 			     struct reftable_ref_record *refs,
 			     size_t nrecords,
 			     struct reftable_log_record *logs,
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 6c2a4e6aa8..5af645048a 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -1,103 +1,10 @@
 #include "unit-test.h"
 #include "hex.h"
 #include "parse-options.h"
-#include "reftable/constants.h"
-#include "reftable/writer.h"
 #include "strbuf.h"
 #include "string-list.h"
 #include "strvec.h"
 
-void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)
-{
-	memset(p, (uint8_t)i, hash_size(id));
-}
-
-static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)
-{
-	strbuf_add(b, data, sz);
-	return sz;
-}
-
-static int strbuf_writer_flush(void *arg UNUSED)
-{
-	return 0;
-}
-
-struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
-						 struct reftable_write_options *opts)
-{
-	struct reftable_writer *writer;
-	int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,
-				      buf, opts);
-	cl_assert(ret == 0);
-	return writer;
-}
-
-void cl_reftable_write_to_buf(struct reftable_buf *buf,
-			     struct reftable_ref_record *refs,
-			     size_t nrefs,
-			     struct reftable_log_record *logs,
-			     size_t nlogs,
-			     struct reftable_write_options *_opts)
-{
-	struct reftable_write_options opts = { 0 };
-	const struct reftable_stats *stats;
-	struct reftable_writer *writer;
-	uint64_t min = 0xffffffff;
-	uint64_t max = 0;
-	int ret;
-
-	if (_opts)
-		opts = *_opts;
-
-	for (size_t i = 0; i < nrefs; i++) {
-		uint64_t ui = refs[i].update_index;
-		if (ui > max)
-			max = ui;
-		if (ui < min)
-			min = ui;
-	}
-	for (size_t i = 0; i < nlogs; i++) {
-		uint64_t ui = logs[i].update_index;
-		if (ui > max)
-			max = ui;
-		if (ui < min)
-			min = ui;
-	}
-
-	writer = cl_reftable_strbuf_writer(buf, &opts);
-	reftable_writer_set_limits(writer, min, max);
-
-	if (nrefs) {
-		ret = reftable_writer_add_refs(writer, refs, nrefs);
-		cl_assert_equal_i(ret, 0);
-	}
-
-	if (nlogs) {
-		ret = reftable_writer_add_logs(writer, logs, nlogs);
-		cl_assert_equal_i(ret, 0);
-	}
-
-	ret = reftable_writer_close(writer);
-	cl_assert_equal_i(ret, 0);
-
-	stats = reftable_writer_stats(writer);
-	for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++) {
-		size_t off = i * (opts.block_size ? opts.block_size
-						  : DEFAULT_BLOCK_SIZE);
-		if (!off)
-			off = header_size(opts.hash_id == REFTABLE_HASH_SHA256 ? 2 : 1);
-		cl_assert(buf->buf[off] == 'r');
-	}
-
-	if (nrefs)
-		cl_assert(stats->ref_stats.blocks > 0);
-	if (nlogs)
-		cl_assert(stats->log_stats.blocks > 0);
-
-	reftable_writer_free(writer);
-}
-
 static const char * const unit_test_usage[] = {
 	N_("unit-test [<options>]"),
 	NULL,
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
index fe0aebd876..85e5d6a948 100644
--- a/t/unit-tests/unit-test.h
+++ b/t/unit-tests/unit-test.h
@@ -1,24 +1,8 @@
 #include "git-compat-util.h"
 #include "clar/clar.h"
 #include "clar-decls.h"
-#include "git-compat-util.h"
-#include "reftable/reftable-writer.h"
 #include "strbuf.h"
 
-struct reftable_buf;
-
-void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
-
-struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
-						 struct reftable_write_options *opts);
-
-void cl_reftable_write_to_buf(struct reftable_buf *buf,
-			     struct reftable_ref_record *refs,
-			     size_t nrecords,
-			     struct reftable_log_record *logs,
-			     size_t nlogs,
-			     struct reftable_write_options *opts);
-
 #define cl_failf(fmt, ...) do { \
 	char desc[4096]; \
 	snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}
  2025-04-29 17:52 ` [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h} Seyi Kuforiji
@ 2025-04-29 23:04   ` Junio C Hamano
  2025-05-02  9:57     ` Patrick Steinhardt
  2025-05-02  9:57   ` Patrick Steinhardt
  1 sibling, 1 reply; 31+ messages in thread
From: Junio C Hamano @ 2025-04-29 23:04 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, ps, phillip.wood

Seyi Kuforiji <kuforiji98@gmail.com> writes:

> Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are
> required for the reftable-related test files to run efficeintly. In the

efficeintly?  effectively?  efficiently?  correctly?  Wouldn't it be
sufficient to say "... to run." without anything else?

> current implementation these functions are designed to conform with our
> homegrown unit-testing structure. So in other to convert the reftable
> test files, there is need for a clar specific implementation of these
> helper functions.

OK.

> type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks;
> i++)`, implement equivalent helper functions in unit-test.{c,h} to use
> clar.

I cannot quite parse this.

> These functions conform with the clar testing framework and become
> available for all reftable-related test files implemented using the clar
> testing framework, which requires them. This will be used by subsequent
> commits.

OK.

> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
> ---
>  t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++
>  t/unit-tests/unit-test.h | 16 +++++++
>  2 files changed, 109 insertions(+)

Hmph, this probably is a question better asked to Patrick, but it
somehow feels a bit unsatisfactory that we are duplicating instead
of replacing, as we cannot see "ah, this removed thing is now added
in a different shape to fit in the other framework" in the patch---
instead what we see in the patch is a bunch of "a completely new
thing that honors the convention we are familiar in existing clar
based tests is added here".


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar
  2025-04-29 17:53 ` [PATCH v2 09/10] t/unit-tests: convert reftable stack " Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  2025-05-05  9:11     ` Seyi Chamber
  0 siblings, 1 reply; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:53:01PM +0100, Seyi Kuforiji wrote:
> diff --git a/t/meson.build b/t/meson.build
> index 756cb2a2dd..8fa00fc9ef 100644
> --- a/t/meson.build
> +++ b/t/meson.build
> @@ -1102,7 +1102,6 @@ integration_tests = [
>  # sufficient to catch missing test suites in our CI though.
>  foreach glob, tests : {
>    't[0-9][0-9][0-9][0-9]-*.sh': integration_tests,
> -  'unit-tests/t-*.c': unit_test_programs,
>    'unit-tests/u-*.c': clar_test_suites,
>  }
>    actual_tests = run_command(shell, '-c', 'ls ' + glob,

Okay. Ideally this would be moved into the next commit where we remove
the infrastructure for our old-style unit tests, but we can't because
the glob matches nothing anymore and thus causes an error.

Also, we have a "check-meson" target in "t/Makefile". Don't we have to
remove unit tests from there, too?

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}
  2025-04-29 23:04   ` Junio C Hamano
@ 2025-05-02  9:57     ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Seyi Kuforiji, git, phillip.wood

On Tue, Apr 29, 2025 at 04:04:22PM -0700, Junio C Hamano wrote:
> Seyi Kuforiji <kuforiji98@gmail.com> writes:
> > Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
> > ---
> >  t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++
> >  t/unit-tests/unit-test.h | 16 +++++++
> >  2 files changed, 109 insertions(+)
> 
> Hmph, this probably is a question better asked to Patrick, but it
> somehow feels a bit unsatisfactory that we are duplicating instead
> of replacing, as we cannot see "ah, this removed thing is now added
> in a different shape to fit in the other framework" in the patch---
> instead what we see in the patch is a bunch of "a completely new
> thing that honors the convention we are familiar in existing clar
> based tests is added here".

Well, my expectation is that at the end of this patch series we will
remove the old functionality. The steps should roughly be:

  1. Implement equivalents for the reftable-specific test library for
     clar.

  2. Convert all reftable unit tests to use the new functions.

  3. Remove the old functions.

Given that (2) is a bunch of tests it makes perfect sense to first have
a separate patch that adds without removing yet.

What I found a bit confusing though is that the new functions aren't
added to "t/unit-tests/lib-reftable.{c,h}". This would be very much
preferable from my point of view as the test functions aren't globally
relevant, but really only relevant to the reftable library.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h}
  2025-04-29 17:52 ` [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h} Seyi Kuforiji
  2025-04-29 23:04   ` Junio C Hamano
@ 2025-05-02  9:57   ` Patrick Steinhardt
  1 sibling, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:52:53PM +0100, Seyi Kuforiji wrote:
> Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are
> required for the reftable-related test files to run efficeintly. In the
> current implementation these functions are designed to conform with our
> homegrown unit-testing structure. So in other to convert the reftable
> test files, there is need for a clar specific implementation of these
> helper functions.
> 
> type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks;
> i++)`, implement equivalent helper functions in unit-test.{c,h} to use
> clar. These functions conform with the clar testing framework and become
> available for all reftable-related test files implemented using the clar
> testing framework, which requires them. This will be used by subsequent
> commits.
> 
> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
> ---
>  t/unit-tests/unit-test.c | 93 ++++++++++++++++++++++++++++++++++++++++
>  t/unit-tests/unit-test.h | 16 +++++++
>  2 files changed, 109 insertions(+)

I think this functionality should be added to
"t/unit-tests/lib-reftable.{c,h}" instead of to the generic unit testing
library as it is highly specific to reftables.

> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
> index 5af645048a..6c2a4e6aa8 100644
> --- a/t/unit-tests/unit-test.c
> +++ b/t/unit-tests/unit-test.c
> @@ -1,10 +1,103 @@
>  #include "unit-test.h"
>  #include "hex.h"
>  #include "parse-options.h"
> +#include "reftable/constants.h"
> +#include "reftable/writer.h"
>  #include "strbuf.h"
>  #include "string-list.h"
>  #include "strvec.h"
>  
> +void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id)
> +{
> +	memset(p, (uint8_t)i, hash_size(id));
> +}
> +
> +static ssize_t strbuf_writer_write(void *b, const void *data, size_t sz)
> +{
> +	strbuf_add(b, data, sz);
> +	return sz;
> +}
> +
> +static int strbuf_writer_flush(void *arg UNUSED)
> +{
> +	return 0;
> +}
> +
> +struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
> +						 struct reftable_write_options *opts)
> +{
> +	struct reftable_writer *writer;
> +	int ret = reftable_writer_new(&writer, &strbuf_writer_write, &strbuf_writer_flush,
> +				      buf, opts);
> +	cl_assert(ret == 0);

We typically don't explicitly compare with zero, so this should rather
be `cl_assert(!ret)`.

> +	return writer;
> +}
> +
> +void cl_reftable_write_to_buf(struct reftable_buf *buf,
> +			     struct reftable_ref_record *refs,
> +			     size_t nrefs,
> +			     struct reftable_log_record *logs,
> +			     size_t nlogs,
> +			     struct reftable_write_options *_opts)
> +{
> +	struct reftable_write_options opts = { 0 };
> +	const struct reftable_stats *stats;
> +	struct reftable_writer *writer;
> +	uint64_t min = 0xffffffff;
> +	uint64_t max = 0;
> +	int ret;
> +
> +	if (_opts)
> +		opts = *_opts;
> +
> +	for (size_t i = 0; i < nrefs; i++) {
> +		uint64_t ui = refs[i].update_index;
> +		if (ui > max)
> +			max = ui;
> +		if (ui < min)
> +			min = ui;
> +	}
> +	for (size_t i = 0; i < nlogs; i++) {
> +		uint64_t ui = logs[i].update_index;
> +		if (ui > max)
> +			max = ui;
> +		if (ui < min)
> +			min = ui;
> +	}
> +
> +	writer = cl_reftable_strbuf_writer(buf, &opts);
> +	reftable_writer_set_limits(writer, min, max);

This function may return an error, as well, so let's verify it while at
it.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework
  2025-04-29 17:52 ` [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:52:54PM +0100, Seyi Kuforiji wrote:
> Adapt reftable basics test file to clar by using clar assertions
> where necessary.Break up test edge case to improve modularity and
> clarity.
> 
> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
> ---
>  Makefile                         |   2 +-
>  t/meson.build                    |   2 +-
>  t/unit-tests/t-reftable-basics.c | 219 -------------------------------
>  t/unit-tests/u-reftable-basics.c | 195 +++++++++++++++++++++++++++
>  4 files changed, 197 insertions(+), 221 deletions(-)
>  delete mode 100644 t/unit-tests/t-reftable-basics.c
>  create mode 100644 t/unit-tests/u-reftable-basics.c
> 
> diff --git a/Makefile b/Makefile
> index 13f9062a05..7b12bb078c 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1362,6 +1362,7 @@ CLAR_TEST_SUITES += u-oid-array
>  CLAR_TEST_SUITES += u-oidmap
>  CLAR_TEST_SUITES += u-oidtree
>  CLAR_TEST_SUITES += u-prio-queue
> +CLAR_TEST_SUITES += u-reftable-basics
>  CLAR_TEST_SUITES += u-reftable-tree
>  CLAR_TEST_SUITES += u-strbuf
>  CLAR_TEST_SUITES += u-strcmp-offset
> @@ -1374,7 +1375,6 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
>  CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
>  CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
>  
> -UNIT_TEST_PROGRAMS += t-reftable-basics
>  UNIT_TEST_PROGRAMS += t-reftable-block
>  UNIT_TEST_PROGRAMS += t-reftable-merged
>  UNIT_TEST_PROGRAMS += t-reftable-pq
> diff --git a/t/meson.build b/t/meson.build
> index bfb744e886..8a42b595d9 100644
> --- a/t/meson.build
> +++ b/t/meson.build
> @@ -8,6 +8,7 @@ clar_test_suites = [
>    'unit-tests/u-oidmap.c',
>    'unit-tests/u-oidtree.c',
>    'unit-tests/u-prio-queue.c',
> +  'unit-tests/u-reftable-basics.c',
>    'unit-tests/u-reftable-tree.c',
>    'unit-tests/u-strbuf.c',
>    'unit-tests/u-strcmp-offset.c',
> @@ -54,7 +55,6 @@ clar_unit_tests = executable('unit-tests',
>  test('unit-tests', clar_unit_tests)
>  
>  unit_test_programs = [
> -  'unit-tests/t-reftable-basics.c',
>    'unit-tests/t-reftable-block.c',
>    'unit-tests/t-reftable-merged.c',
>    'unit-tests/t-reftable-pq.c',
> diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c
> deleted file mode 100644
> index c9e751e49e..0000000000
> --- a/t/unit-tests/t-reftable-basics.c
> +++ /dev/null
> @@ -1,219 +0,0 @@
> -/*
> -Copyright 2020 Google LLC
> -
> -Use of this source code is governed by a BSD-style
> -license that can be found in the LICENSE file or at
> -https://developers.google.com/open-source/licenses/bsd
> -*/
> -
> -#include "test-lib.h"
> -#include "reftable/basics.h"
> -
> -struct integer_needle_lesseq_args {
> -	int needle;
> -	int *haystack;
> -};
> -
> -static int integer_needle_lesseq(size_t i, void *_args)
> -{
> -	struct integer_needle_lesseq_args *args = _args;
> -	return args->needle <= args->haystack[i];
> -}
> -
> -static void *realloc_stub(void *p UNUSED, size_t size UNUSED)
> -{
> -	return NULL;
> -}
> -
> -int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
> -{
> -	if_test ("binary search with binsearch works") {
> -		int haystack[] = { 2, 4, 6, 8, 10 };
> -		struct {
> -			int needle;
> -			size_t expected_idx;
> -		} testcases[] = {
> -			{-9000, 0},
> -			{-1, 0},
> -			{0, 0},
> -			{2, 0},
> -			{3, 1},
> -			{4, 1},
> -			{7, 3},
> -			{9, 4},
> -			{10, 4},
> -			{11, 5},
> -			{9000, 5},
> -		};
> -
> -		for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
> -			struct integer_needle_lesseq_args args = {
> -				.haystack = haystack,
> -				.needle = testcases[i].needle,
> -			};
> -			size_t idx;
> -
> -			idx = binsearch(ARRAY_SIZE(haystack),
> -					&integer_needle_lesseq, &args);
> -			check_int(idx, ==, testcases[i].expected_idx);
> -		}
> -	}
> -
> -	if_test ("names_length returns size of a NULL-terminated string array") {
> -		const char *a[] = { "a", "b", NULL };
> -		check_int(names_length(a), ==, 2);
> -	}
> -
> -	if_test ("names_equal compares NULL-terminated string arrays") {
> -		const char *a[] = { "a", "b", "c", NULL };
> -		const char *b[] = { "a", "b", "d", NULL };
> -		const char *c[] = { "a", "b", NULL };
> -
> -		check(names_equal(a, a));
> -		check(!names_equal(a, b));
> -		check(!names_equal(a, c));
> -	}
> -
> -	if_test ("parse_names works for basic input") {
> -		char in1[] = "line\n";
> -		char in2[] = "a\nb\nc";
> -		char **out = parse_names(in1, strlen(in1));
> -		check(out != NULL);
> -		check_str(out[0], "line");
> -		check(!out[1]);
> -		free_names(out);
> -
> -		out = parse_names(in2, strlen(in2));
> -		check(out != NULL);
> -		check_str(out[0], "a");
> -		check_str(out[1], "b");
> -		check_str(out[2], "c");
> -		check(!out[3]);
> -		free_names(out);
> -	}
> -
> -	if_test ("parse_names drops empty string") {
> -		char in[] = "a\n\nb\n";
> -		char **out = parse_names(in, strlen(in));
> -		check(out != NULL);
> -		check_str(out[0], "a");
> -		/* simply '\n' should be dropped as empty string */
> -		check_str(out[1], "b");
> -		check(!out[2]);
> -		free_names(out);
> -	}
> -
> -	if_test ("common_prefix_size works") {
> -		struct reftable_buf a = REFTABLE_BUF_INIT;
> -		struct reftable_buf b = REFTABLE_BUF_INIT;
> -		struct {
> -			const char *a, *b;
> -			int want;
> -		} cases[] = {
> -			{"abcdef", "abc", 3},
> -			{ "abc", "ab", 2 },
> -			{ "", "abc", 0 },
> -			{ "abc", "abd", 2 },
> -			{ "abc", "pqr", 0 },
> -		};
> -
> -		for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
> -			check(!reftable_buf_addstr(&a, cases[i].a));
> -			check(!reftable_buf_addstr(&b, cases[i].b));
> -			check_uint(common_prefix_size(&a, &b), ==, cases[i].want);
> -			reftable_buf_reset(&a);
> -			reftable_buf_reset(&b);
> -		}
> -		reftable_buf_release(&a);
> -		reftable_buf_release(&b);
> -	}
> -
> -	if_test ("reftable_put_be64 and reftable_get_be64 work") {
> -		uint64_t in = 0x1122334455667788;
> -		uint8_t dest[8];
> -		uint64_t out;
> -		reftable_put_be64(dest, in);
> -		out = reftable_get_be64(dest);
> -		check_int(in, ==, out);
> -	}
> -
> -	if_test ("reftable_put_be32 and reftable_get_be32 work") {
> -		uint32_t in = 0x11223344;
> -		uint8_t dest[4];
> -		uint32_t out;
> -		reftable_put_be32(dest, in);
> -		out = reftable_get_be32(dest);
> -		check_int(in, ==, out);
> -	}
> -
> -	if_test ("reftable_put_be24 and reftable_get_be24 work") {
> -		uint32_t in = 0x112233;
> -		uint8_t dest[3];
> -		uint32_t out;
> -		reftable_put_be24(dest, in);
> -		out = reftable_get_be24(dest);
> -		check_int(in, ==, out);
> -	}
> -
> -	if_test ("put_be16 and get_be16 work") {
> -		uint32_t in = 0xfef1;
> -		uint8_t dest[3];
> -		uint32_t out;
> -		reftable_put_be16(dest, in);
> -		out = reftable_get_be16(dest);
> -		check_int(in, ==, out);
> -	}
> -
> -	if_test ("REFTABLE_ALLOC_GROW works") {
> -		int *arr = NULL, *old_arr;
> -		size_t alloc = 0, old_alloc;
> -
> -		check(!REFTABLE_ALLOC_GROW(arr, 1, alloc));
> -		check(arr != NULL);
> -		check_uint(alloc, >=, 1);
> -		arr[0] = 42;
> -
> -		old_alloc = alloc;
> -		old_arr = arr;
> -		reftable_set_alloc(NULL, realloc_stub, NULL);
> -		check(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
> -		check(arr == old_arr);
> -		check_uint(alloc, ==, old_alloc);
> -
> -		old_alloc = alloc;
> -		reftable_set_alloc(NULL, NULL, NULL);
> -		check(!REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
> -		check(arr != NULL);
> -		check_uint(alloc, >, old_alloc);
> -		arr[alloc - 1] = 42;
> -
> -		reftable_free(arr);
> -	}
> -
> -	if_test ("REFTABLE_ALLOC_GROW_OR_NULL works") {
> -		int *arr = NULL;
> -		size_t alloc = 0, old_alloc;
> -
> -		REFTABLE_ALLOC_GROW_OR_NULL(arr, 1, alloc);
> -		check(arr != NULL);
> -		check_uint(alloc, >=, 1);
> -		arr[0] = 42;
> -
> -		old_alloc = alloc;
> -		REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
> -		check(arr != NULL);
> -		check_uint(alloc, >, old_alloc);
> -		arr[alloc - 1] = 42;
> -
> -		old_alloc = alloc;
> -		reftable_set_alloc(NULL, realloc_stub, NULL);
> -		REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
> -		check(arr == NULL);
> -		check_uint(alloc, ==, 0);
> -		reftable_set_alloc(NULL, NULL, NULL);
> -
> -		reftable_free(arr);
> -	}
> -
> -	return test_done();
> -}
> diff --git a/t/unit-tests/u-reftable-basics.c b/t/unit-tests/u-reftable-basics.c
> new file mode 100644
> index 0000000000..63dd568faf
> --- /dev/null
> +++ b/t/unit-tests/u-reftable-basics.c
> @@ -0,0 +1,195 @@
> +/*
> +Copyright 2020 Google LLC
> +
> +Use of this source code is governed by a BSD-style
> +license that can be found in the LICENSE file or at
> +https://developers.google.com/open-source/licenses/bsd
> +*/
> +
> +#include "unit-test.h"
> +#include "reftable/basics.h"
> +
> +struct integer_needle_lesseq_args {
> +	int needle;
> +	int *haystack;
> +};
> +
> +static int integer_needle_lesseq(size_t i, void *_args)
> +{
> +	struct integer_needle_lesseq_args *args = _args;
> +	return args->needle <= args->haystack[i];
> +}
> +
> +static void *realloc_stub(void *p UNUSED, size_t size UNUSED)
> +{
> +	return NULL;
> +}
> +
> +void test_reftable_basics__binsearch(void)
> +{
> +	int haystack[] = { 2, 4, 6, 8, 10 };
> +	struct {
> +		int needle;
> +		size_t expected_idx;
> +	} testcases[] = {
> +		{-9000, 0},
> +		{-1, 0},
> +		{0, 0},
> +		{2, 0},
> +		{3, 1},
> +		{4, 1},
> +		{7, 3},
> +		{9, 4},
> +		{10, 4},
> +		{11, 5},
> +		{9000, 5},
> +	};
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
> +		struct integer_needle_lesseq_args args = {
> +			.haystack = haystack,
> +			.needle = testcases[i].needle,
> +		};
> +		size_t idx;
> +
> +		idx = binsearch(ARRAY_SIZE(haystack),
> +				&integer_needle_lesseq, &args);
> +		cl_assert_equal_i(idx, testcases[i].expected_idx);
> +	}
> +

Nit: empty newline can be removed.

> +}
> +
> +void test_reftable_basics__names_length(void)
> +{
> +	const char *a[] = { "a", "b", NULL };
> +	cl_assert_equal_i(names_length(a), 2);
> +}
> +
> +void test_reftable_basics__names_equal(void)
> +{
> +	const char *a[] = { "a", "b", "c", NULL };
> +	const char *b[] = { "a", "b", "d", NULL };
> +	const char *c[] = { "a", "b", NULL };
> +
> +	cl_assert(names_equal(a, a));
> +	cl_assert(!names_equal(a, b));
> +	cl_assert(!names_equal(a, c));
> +}
> +
> +void test_reftable_basics__parse_names(void)
> +{
> +	char in1[] = "line\n";
> +	char in2[] = "a\nb\nc";
> +	char **out = parse_names(in1, strlen(in1));
> +	cl_assert(out != NULL);
> +	cl_assert_equal_s(out[0], "line");
> +	cl_assert(!out[1]);
> +	free_names(out);
> +
> +	out = parse_names(in2, strlen(in2));
> +	cl_assert(out != NULL);
> +	cl_assert_equal_s(out[0], "a");
> +	cl_assert_equal_s(out[1], "b");
> +	cl_assert_equal_s(out[2], "c");
> +	cl_assert(!out[3]);
> +	free_names(out);
> +}
> +

I think you missed converting "parse_names drops empty string".

> +void test_reftable_basics__common_prefix_size(void)
> +{
> +	struct reftable_buf a = REFTABLE_BUF_INIT;
> +	struct reftable_buf b = REFTABLE_BUF_INIT;
> +	struct {
> +		const char *a, *b;
> +		int want;
> +	} cases[] = {
> +		{"abcdef", "abc", 3},
> +		{ "abc", "ab", 2 },
> +		{ "", "abc", 0 },
> +		{ "abc", "abd", 2 },
> +		{ "abc", "pqr", 0 },
> +	};
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
> +		reftable_buf_reset(&a);
> +		reftable_buf_reset(&b);
> +		cl_assert_equal_i(reftable_buf_addstr(&a, cases[i].a), 0);
> +		cl_assert_equal_i(reftable_buf_addstr(&b, cases[i].b), 0);
> +		cl_assert_equal_i(common_prefix_size(&a, &b), cases[i].want);

Why did you change the order of `reftable_buf_reset()` calls? The
reordered logic achieves the same result, but I'd recommend to keep
things as-is so that reviewers aren't puzzled by this arbitrary change.

> +	}
> +	reftable_buf_release(&a);
> +	reftable_buf_release(&b);
> +}

I miss tests for `put_be64` and `put_be32`.

> +void test_reftable_basics__put_get_be24(void)
> +{
> +	uint32_t in = 0x112233;
> +	uint8_t dest[3];
> +	uint32_t out;
> +	reftable_put_be24(dest, in);
> +	out = reftable_get_be24(dest);
> +	cl_assert_equal_i(in, out);
> +}
> +
> +void test_reftable_basics__put_get_be16(void)
> +{
> +	uint32_t in = 0xfef1;
> +	uint8_t dest[3];
> +	uint32_t out;
> +	reftable_put_be16(dest, in);
> +	out = reftable_get_be16(dest);
> +	cl_assert_equal_i(in, out);
> +}
> +
> +void test_reftable_basics__grow_alloc(void)

Let's rename this to `__alloc_grow()` to match the name of the function.

> +{
> +	int *arr = NULL, *old_arr;
> +	size_t alloc = 0, old_alloc;
> +
> +	cl_assert_equal_i(REFTABLE_ALLOC_GROW(arr, 1, alloc), 0);
> +	cl_assert(arr != NULL);
> +	cl_assert(alloc >= 1);
> +	arr[0] = 42;
> +
> +	old_alloc = alloc;
> +	old_arr = arr;
> +	reftable_set_alloc(NULL, realloc_stub, NULL);
> +	cl_assert(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
> +	cl_assert(arr == old_arr);
> +	cl_assert_equal_i(alloc, old_alloc);
> +
> +	old_alloc = alloc;
> +	reftable_set_alloc(NULL, NULL, NULL);
> +	cl_assert_equal_i(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc), 0);
> +	cl_assert(arr != NULL);
> +	cl_assert(alloc > old_alloc);
> +	arr[alloc - 1] = 42;
> +
> +	reftable_free(arr);
> +}
> +
> +void test_reftable_basics__grow_alloc_or_null(void)

Same here, let's rename to `alloc_grow_or_null`.

> +{
> +	int *arr = NULL;
> +	size_t alloc = 0, old_alloc;
> +
> +	REFTABLE_ALLOC_GROW_OR_NULL(arr, 1, alloc);
> +	cl_assert(arr != NULL);
> +	cl_assert(alloc >= 1);
> +	arr[0] = 42;
> +
> +	old_alloc = alloc;
> +	REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
> +	cl_assert(arr != NULL);
> +	cl_assert(alloc > old_alloc);
> +	arr[alloc - 1] = 42;
> +
> +	old_alloc = alloc;
> +	reftable_set_alloc(NULL, realloc_stub, NULL);
> +	REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc);
> +	cl_assert(arr == NULL);
> +	cl_assert_equal_i(alloc, 0);
> +	reftable_set_alloc(NULL, NULL, NULL);
> +
> +	reftable_free(arr);
> +}

Other than that this looks well-done to me, thanks!

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 07/10] t/unit-tests: convert reftable readwrite test to use clar
  2025-04-29 17:52 ` [PATCH v2 07/10] t/unit-tests: convert reftable readwrite " Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:52:59PM +0100, Seyi Kuforiji wrote:
> diff --git a/t/unit-tests/u-reftable-readwrite.c b/t/unit-tests/u-reftable-readwrite.c
> new file mode 100644
> index 0000000000..3d6bdcfceb
> --- /dev/null
> +++ b/t/unit-tests/u-reftable-readwrite.c
> @@ -0,0 +1,870 @@
> +/*
> +Copyright 2020 Google LLC
> +
> +Use of this source code is governed by a BSD-style
> +license that can be found in the LICENSE file or at
> +https://developers.google.com/open-source/licenses/bsd
> +*/
> +
> +#define DISABLE_SIGN_COMPARE_WARNINGS
> +
> +#include "unit-test.h"
> +#include "lib-reftable.h"
> +#include "reftable/basics.h"
> +#include "reftable/blocksource.h"
> +#include "reftable/reader.h"
> +#include "reftable/reftable-error.h"
> +#include "reftable/reftable-writer.h"
> +#include "strbuf.h"
> +
> +static const int update_index = 5;
> +
> +void test_reftable_readwrite__buffer(void)
> +{
> +	struct reftable_buf buf = REFTABLE_BUF_INIT;
> +	struct reftable_block_source source = { 0 };
> +	struct reftable_block out = { 0 };
> +	int n;
> +	uint8_t in[] = "hello";
> +	cl_assert(reftable_buf_add(&buf, in, sizeof(in)) == 0);
> +	block_source_from_buf(&source, &buf);
> +	cl_assert_equal_i(block_source_size(&source), 6);
> +	n = block_source_read_block(&source, &out, 0, sizeof(in));
> +	cl_assert_equal_i(n, sizeof(in));
> +	cl_assert(memcmp(in, out.data, n) == 0);

It feels inconsisetnt that we use `cl_assert_equal_i()` to check for `n`
but `cl_assert(... == 0)` here.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar
  2025-04-29 17:52 ` [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  2025-05-05  7:37     ` Seyi Chamber
  0 siblings, 1 reply; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:52:55PM +0100, Seyi Kuforiji wrote:
> diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c
> deleted file mode 100644
> index 22040aeefa..0000000000
> --- a/t/unit-tests/t-reftable-block.c
> +++ /dev/null

Hm, why is this recorded as a delete and creation? Weird, inspecting the
diff locally properly shows it as a rename, which makes it a ton easier
to review. It would be great if you could try to play around with the
`--find-renames` option in the next iteration of this series and double
check that these are shown as a rename.

> diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c
> new file mode 100644
> index 0000000000..af24901230
> --- /dev/null
> +++ b/t/unit-tests/u-reftable-block.c
> @@ -0,0 +1,373 @@
> +/*
> +Copyright 2020 Google LLC
> +
> +Use of this source code is governed by a BSD-style
> +license that can be found in the LICENSE file or at
> +https://developers.google.com/open-source/licenses/bsd
> +*/
> +
> +#include "unit-test.h"
> +#include "reftable/block.h"
> +#include "reftable/blocksource.h"
> +#include "reftable/constants.h"
> +#include "reftable/reftable-error.h"
> +#include "strbuf.h"
> +
> +void test_reftable_block__index_read_write(void)

This doesn't got to do anything with indices but with refs, so I'd
rename this to `__ref_read_write()`.

> +{
> +	const int header_off = 21; /* random */
> +	struct reftable_record recs[30];
> +	const size_t N = ARRAY_SIZE(recs);
> +	const size_t block_size = 1024;
> +	struct reftable_block block = { 0 };
> +	struct block_writer bw = {
> +		.last_key = REFTABLE_BUF_INIT,
> +	};
> +	struct reftable_record rec = {
> +		.type = BLOCK_TYPE_REF,
> +	};
> +	size_t i = 0;
> +	int ret;
> +	struct block_reader br = { 0 };
> +	struct block_iter it = BLOCK_ITER_INIT;
> +	struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
> +
> +	REFTABLE_CALLOC_ARRAY(block.data, block_size);
> +	cl_assert(block.data != NULL);
> +	block.len = block_size;
> +	block_source_from_buf(&block.source ,&buf);
> +	ret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
> +				header_off, hash_size(REFTABLE_HASH_SHA1));
> +	cl_assert(ret == 0);

Same comment here, asserts like this can be retained as
`cl_assert(!ret)`.

> +	rec.u.ref.refname = (char *) "";
> +	rec.u.ref.value_type = REFTABLE_REF_DELETION;
> +	ret = block_writer_add(&bw, &rec);
> +	cl_assert_equal_i(ret, REFTABLE_API_ERROR);
> +
> +	for (i = 0; i < N; i++) {
> +		rec.u.ref.refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
> +		rec.u.ref.value_type = REFTABLE_REF_VAL1;
> +		memset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);
> +
> +		recs[i] = rec;
> +		ret = block_writer_add(&bw, &rec);
> +		rec.u.ref.refname = NULL;
> +		rec.u.ref.value_type = REFTABLE_REF_DELETION;
> +		cl_assert_equal_i(ret, 0);
> +	}
> +
> +	ret = block_writer_finish(&bw);
> +	cl_assert(ret > 0);

It's a bit unfortunate that we have to use `cl_assert()` here, but that
isn't the fault of this series. I do have a pull request pending
upstream that introduces integer comparisons. Once we've updated to that
version I'll go through our unit tests and adapt callsites accordingly.

[snip]
> +void test_reftable_block__ref_read_write(void)

This one here should be called `__index_read_write()`. I guess you
confused the first and and this test name with one another.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 04/10] t/unit-tests: convert reftable merged test to use clar
  2025-04-29 17:52 ` [PATCH v2 04/10] t/unit-tests: convert reftable merged " Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:52:56PM +0100, Seyi Kuforiji wrote:
> diff --git a/t/unit-tests/u-reftable-merged.c b/t/unit-tests/u-reftable-merged.c
> new file mode 100644
> index 0000000000..48c8f9f6b5
> --- /dev/null
> +++ b/t/unit-tests/u-reftable-merged.c
> @@ -0,0 +1,515 @@
> +/*
> +Copyright 2020 Google LLC
> +
> +Use of this source code is governed by a BSD-style
> +license that can be found in the LICENSE file or at
> +https://developers.google.com/open-source/licenses/bsd
> +*/
> +
> +#include "unit-test.h"
> +#include "lib-reftable.h"
> +#include "reftable/blocksource.h"
> +#include "reftable/constants.h"
> +#include "reftable/merged.h"
> +#include "reftable/reader.h"
> +#include "reftable/reftable-error.h"
> +#include "reftable/reftable-merged.h"
> +#include "reftable/reftable-writer.h"
> +
> +static struct reftable_merged_table *
> +merged_table_from_records(struct reftable_ref_record **refs,
> +			  struct reftable_block_source **source,
> +			  struct reftable_reader ***readers, const size_t *sizes,
> +			  struct reftable_buf *buf, const size_t n)
> +{
> +	struct reftable_merged_table *mt = NULL;
> +	struct reftable_write_options opts = {
> +		.block_size = 256,
> +	};
> +	int err;
> +
> +	REFTABLE_CALLOC_ARRAY(*readers, n);
> +	cl_assert(*readers != NULL);
> +	REFTABLE_CALLOC_ARRAY(*source, n);
> +	cl_assert(*source != NULL);
> +
> +	for (size_t i = 0; i < n; i++) {
> +		cl_reftable_write_to_buf(&buf[i], refs[i], sizes[i], NULL, 0, &opts);
> +		block_source_from_buf(&(*source)[i], &buf[i]);
> +
> +		err = reftable_reader_new(&(*readers)[i], &(*source)[i],
> +					  "name");
> +		cl_assert(err == 0);
> +	}
> +
> +	err = reftable_merged_table_new(&mt, *readers, n, REFTABLE_HASH_SHA1);
> +	cl_assert(err == 0);
> +	return mt;
> +}
> +
> +static void readers_destroy(struct reftable_reader **readers, const size_t n)
> +{
> +	for (size_t i = 0; i < n; i++)
> +		reftable_reader_decref(readers[i]);
> +	reftable_free(readers);
> +}
> +
> +void test_reftable_merged__merged_single_record(void)

This should be `__single_record(void)`. Same for the others, the
`__merged` prefix is somewhat duplicate as you already have it in the
test suite name.

> +void test_reftable_merged__merged_seek_multiple_times(void)

So, same here, let's rename to `__seek_multiple_times()`.

> +{
> +	struct reftable_ref_record r1[] = {
> +		{
> +			.refname = (char *) "a",
> +			.update_index = 1,
> +			.value_type = REFTABLE_REF_VAL1,
> +			.value.val1 = { 1 },
> +		},
> +		{
> +			.refname = (char *) "c",
> +			.update_index = 1,
> +			.value_type = REFTABLE_REF_VAL1,
> +			.value.val1 = { 2 },
> +		}
> +	};
> +	struct reftable_ref_record r2[] = {
> +		{
> +			.refname = (char *) "b",
> +			.update_index = 2,
> +			.value_type = REFTABLE_REF_VAL1,
> +			.value.val1 = { 3 },
> +		},
> +		{
> +			.refname = (char *) "d",
> +			.update_index = 2,
> +			.value_type = REFTABLE_REF_VAL1,
> +			.value.val1 = { 4 },
> +		},
> +	};
> +	struct reftable_ref_record *refs[] = {
> +		r1, r2,
> +	};
> +	size_t sizes[] = {
> +		ARRAY_SIZE(r1), ARRAY_SIZE(r2),
> +	};
> +	struct reftable_buf bufs[] = {
> +		REFTABLE_BUF_INIT, REFTABLE_BUF_INIT,
> +	};
> +	struct reftable_block_source *sources = NULL;
> +	struct reftable_reader **readers = NULL;
> +	struct reftable_ref_record rec = { 0 };
> +	struct reftable_iterator it = { 0 };
> +	struct reftable_merged_table *mt;
> +
> +	mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
> +	merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
> +
> +	for (size_t i = 0; i < 5; i++) {
> +		int err = reftable_iterator_seek_ref(&it, "c");
> +		cl_assert(err == 0);
> +
> +		cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
> +		cl_assert_equal_i(reftable_ref_record_equal(&rec, &r1[1],
> +													REFTABLE_HASH_SIZE_SHA1), 1);

Indentation is wrong. Our tabs are 8 spaces, so this is indented too
deep now.

> +		cl_assert(reftable_iterator_next_ref(&it, &rec) == 0);
> +		cl_assert_equal_i(reftable_ref_record_equal(&rec, &r2[1],
> +													REFTABLE_HASH_SIZE_SHA1), 1);

Same here. There are also other instances in this file.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 06/10] t/unit-tests: convert reftable reader test to use clar
  2025-04-29 17:52 ` [PATCH v2 06/10] t/unit-tests: convert reftable reader " Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:52:58PM +0100, Seyi Kuforiji wrote:
> diff --git a/t/unit-tests/u-reftable-reader.c b/t/unit-tests/u-reftable-reader.c
> new file mode 100644
> index 0000000000..6c35063105
> --- /dev/null
> +++ b/t/unit-tests/u-reftable-reader.c
> @@ -0,0 +1,78 @@
> +#include "unit-test.h"
> +#include "lib-reftable.h"
> +#include "reftable/blocksource.h"
> +#include "reftable/reader.h"
> +
> +void test_reftable_reader__seek_once(void)
> +{
> +	struct reftable_ref_record records[] = {
> +		{
> +			.refname = (char *) "refs/heads/main",
> +			.value_type = REFTABLE_REF_VAL1,
> +			.value.val1 = { 42 },
> +		},
> +	};
> +	struct reftable_block_source source = { 0 };
> +	struct reftable_ref_record ref = { 0 };
> +	struct reftable_iterator it = { 0 };
> +	struct reftable_reader *reader;
> +	struct reftable_buf buf = REFTABLE_BUF_INIT;
> +
> +	cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
> +	block_source_from_buf(&source, &buf);
> +
> +	cl_assert(reftable_reader_new(&reader, &source, "name") == 0);
> +
> +

Nit: empty newline.

> +	reftable_reader_init_ref_iterator(reader, &it);
> +	cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
> +	cl_assert(reftable_iterator_next_ref(&it, &ref) == 0);
> +
> +	cl_assert_equal_i(reftable_ref_record_equal(&ref, &records[0],
> +												REFTABLE_HASH_SIZE_SHA1), 1);
> +

Indentation is wrong again.

> +	cl_assert_equal_i(reftable_iterator_next_ref(&it, &ref), 1);
> +
> +	reftable_ref_record_release(&ref);
> +	reftable_iterator_destroy(&it);
> +	reftable_reader_decref(reader);
> +	reftable_buf_release(&buf);
> +}
> +
> +void test_reftable_reader__reseek(void)
> +{
> +	struct reftable_ref_record records[] = {
> +		{
> +			.refname = (char *) "refs/heads/main",
> +			.value_type = REFTABLE_REF_VAL1,
> +			.value.val1 = { 42 },
> +		},
> +	};
> +	struct reftable_block_source source = { 0 };
> +	struct reftable_ref_record ref = { 0 };
> +	struct reftable_iterator it = { 0 };
> +	struct reftable_reader *reader;
> +	struct reftable_buf buf = REFTABLE_BUF_INIT;
> +
> +	cl_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
> +	block_source_from_buf(&source, &buf);
> +
> +	cl_assert(reftable_reader_new(&reader, &source, "name") == 0);
> +
> +	reftable_reader_init_ref_iterator(reader, &it);
> +
> +	for (size_t i = 0; i < 5; i++) {
> +		cl_assert(reftable_iterator_seek_ref(&it, "") == 0);
> +		cl_assert(reftable_iterator_next_ref(&it, &ref) == 0);
> +
> +		cl_assert_equal_i(reftable_ref_record_equal(&ref, &records[0],
> +													REFTABLE_HASH_SIZE_SHA1), 1);

Here, as well.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
  2025-04-29 17:53 ` [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar Seyi Kuforiji
@ 2025-05-02  9:57   ` Patrick Steinhardt
  2025-05-05  7:27     ` Seyi Chamber
  0 siblings, 1 reply; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  9:57 UTC (permalink / raw)
  To: Seyi Kuforiji; +Cc: git, phillip.wood

On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:
> Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are
> required for the reftable-related test files to run efficeintly. In the
> current implementation these functions are designed to conform with our
> homegrown unit-testing structure. So in other to convert the reftable
> test files, there is need for a clar specific implementation of these
> helper functions.
> 
> type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++)`
> Adapt functions in lib-reftable.{c,h} to use clar. These functions
> conform with the clar testing framework and become available for all
> reftable-related test files implemented using the clar testing
> framework, which requires them. This change migrates the helper
> functions back into `lib-reftable.{c,h}`.
> 
> Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
> ---
>  Makefile                    |  4 +-
>  t/meson.build               |  4 +-
>  t/unit-tests/lib-reftable.c | 26 +++++------
>  t/unit-tests/lib-reftable.h |  6 +--
>  t/unit-tests/unit-test.c    | 93 -------------------------------------
>  t/unit-tests/unit-test.h    | 16 -------
>  6 files changed, 20 insertions(+), 129 deletions(-)
> 
> diff --git a/Makefile b/Makefile
> index 0b42893611..7e646e16ee 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1379,12 +1379,12 @@ CLAR_TEST_SUITES += u-urlmatch-normalization
>  CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
>  CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES))
>  CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
> -CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
>  CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
> +CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
> +CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
>  
>  UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
>  UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
> -UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
>  

`UNIT_TEST_PROGS` only contains the "unit-test" binary now, right? Does
this mean that we can simplify build rules in our Makefile now?

> diff --git a/t/meson.build b/t/meson.build
> index 8fa00fc9ef..7c305a90b5 100644
> --- a/t/meson.build
> +++ b/t/meson.build
> @@ -26,8 +26,9 @@ clar_test_suites = [
>  
>  clar_sources = [
>    'unit-tests/clar/clar.c',
> +  'unit-tests/lib-oid.c',
> +  'unit-tests/lib-reftable.c',
>    'unit-tests/unit-test.c',
> -  'unit-tests/lib-oid.c'
>  ]
>  
>  clar_decls_h = custom_target(
> @@ -69,7 +70,6 @@ foreach unit_test_program : unit_test_programs
>    unit_test = executable(unit_test_name,
>      sources: [
>        'unit-tests/test-lib.c',
> -      'unit-tests/lib-reftable.c',
>        unit_test_program,
>      ],
>      dependencies: [libgit_commonmain],

Can't we remove this completely? `unit_test_programs` is empty now, so
this loop is a no-op now.

> diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
> index e4c360fa7e..2958db5dc0 100644
> --- a/t/unit-tests/lib-reftable.h
> +++ b/t/unit-tests/lib-reftable.h
> @@ -6,12 +6,12 @@
>  
>  struct reftable_buf;
>  
> -void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> +void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
>  
> -struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
> +struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
>  						 struct reftable_write_options *opts);
>  
> -void t_reftable_write_to_buf(struct reftable_buf *buf,
> +void cl_reftable_write_to_buf(struct reftable_buf *buf,
>  			     struct reftable_ref_record *refs,
>  			     size_t nrecords,
>  			     struct reftable_log_record *logs,

It is quite weird that we declare the replacement functions in
"unit-test.h" in the first commit only to remove them at a later point.
It would make way more sense if we introduced the functions in
"t/unit/lib-reftable.{c,h}" right from the start and then only remove
the unused functions in the last step.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
  2025-05-02  9:57   ` Patrick Steinhardt
@ 2025-05-05  7:27     ` Seyi Chamber
  2025-05-05  9:52       ` Patrick Steinhardt
  0 siblings, 1 reply; 31+ messages in thread
From: Seyi Chamber @ 2025-05-05  7:27 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, phillip.wood

Hi Patrick,
On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:
>
> On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:
> > Helper functions defined in `t/unit-tests/lib-reftable.{c,h}` are
> > required for the reftable-related test files to run efficeintly. In the
> > current implementation these functions are designed to conform with our
> > homegrown unit-testing structure. So in other to convert the reftable
> > test files, there is need for a clar specific implementation of these
> > helper functions.
> >
> > type cast `for (size_t i = 0; i < (size_t)stats->ref_stats.blocks; i++)`
> > Adapt functions in lib-reftable.{c,h} to use clar. These functions
> > conform with the clar testing framework and become available for all
> > reftable-related test files implemented using the clar testing
> > framework, which requires them. This change migrates the helper
> > functions back into `lib-reftable.{c,h}`.
> >
> > Signed-off-by: Seyi Kuforiji <kuforiji98@gmail.com>
> > ---
> >  Makefile                    |  4 +-
> >  t/meson.build               |  4 +-
> >  t/unit-tests/lib-reftable.c | 26 +++++------
> >  t/unit-tests/lib-reftable.h |  6 +--
> >  t/unit-tests/unit-test.c    | 93 -------------------------------------
> >  t/unit-tests/unit-test.h    | 16 -------
> >  6 files changed, 20 insertions(+), 129 deletions(-)
> >
> > diff --git a/Makefile b/Makefile
> > index 0b42893611..7e646e16ee 100644
> > --- a/Makefile
> > +++ b/Makefile
> > @@ -1379,12 +1379,12 @@ CLAR_TEST_SUITES += u-urlmatch-normalization
> >  CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
> >  CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES))
> >  CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
> > -CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
> >  CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-oid.o
> > +CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
> > +CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
> >
> >  UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
> >  UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
> > -UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/lib-reftable.o
> >
>
> `UNIT_TEST_PROGS` only contains the "unit-test" binary now, right? Does
> this mean that we can simplify build rules in our Makefile now?
>
> > diff --git a/t/meson.build b/t/meson.build
> > index 8fa00fc9ef..7c305a90b5 100644
> > --- a/t/meson.build
> > +++ b/t/meson.build
> > @@ -26,8 +26,9 @@ clar_test_suites = [
> >
> >  clar_sources = [
> >    'unit-tests/clar/clar.c',
> > +  'unit-tests/lib-oid.c',
> > +  'unit-tests/lib-reftable.c',
> >    'unit-tests/unit-test.c',
> > -  'unit-tests/lib-oid.c'
> >  ]
> >
> >  clar_decls_h = custom_target(
> > @@ -69,7 +70,6 @@ foreach unit_test_program : unit_test_programs
> >    unit_test = executable(unit_test_name,
> >      sources: [
> >        'unit-tests/test-lib.c',
> > -      'unit-tests/lib-reftable.c',
> >        unit_test_program,
> >      ],
> >      dependencies: [libgit_commonmain],
>
> Can't we remove this completely? `unit_test_programs` is empty now, so
> this loop is a no-op now.
>
> > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
> > index e4c360fa7e..2958db5dc0 100644
> > --- a/t/unit-tests/lib-reftable.h
> > +++ b/t/unit-tests/lib-reftable.h
> > @@ -6,12 +6,12 @@
> >
> >  struct reftable_buf;
> >
> > -void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > +void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> >
> > -struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
> > +struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
> >                                                struct reftable_write_options *opts);
> >
> > -void t_reftable_write_to_buf(struct reftable_buf *buf,
> > +void cl_reftable_write_to_buf(struct reftable_buf *buf,
> >                            struct reftable_ref_record *refs,
> >                            size_t nrecords,
> >                            struct reftable_log_record *logs,
>
> It is quite weird that we declare the replacement functions in
> "unit-test.h" in the first commit only to remove them at a later point.
> It would make way more sense if we introduced the functions in
> "t/unit/lib-reftable.{c,h}" right from the start and then only remove
> the unused functions in the last step.
>
> Patrick

If I get it correctly, you're suggesting I have both the original
functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`
right from the first commit, right?

Thanks
Seyi

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar
  2025-05-02  9:57   ` Patrick Steinhardt
@ 2025-05-05  7:37     ` Seyi Chamber
  2025-05-05  9:52       ` Patrick Steinhardt
  0 siblings, 1 reply; 31+ messages in thread
From: Seyi Chamber @ 2025-05-05  7:37 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, phillip.wood

On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:
>
> On Tue, Apr 29, 2025 at 06:52:55PM +0100, Seyi Kuforiji wrote:
> > diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c
> > deleted file mode 100644
> > index 22040aeefa..0000000000
> > --- a/t/unit-tests/t-reftable-block.c
> > +++ /dev/null
>
> Hm, why is this recorded as a delete and creation? Weird, inspecting the
> diff locally properly shows it as a rename, which makes it a ton easier
> to review. It would be great if you could try to play around with the
> `--find-renames` option in the next iteration of this series and double
> check that these are shown as a rename.
>

I used `--find-renames=90` for this patch series. Is there any
recommended number to set it to? :/

> > diff --git a/t/unit-tests/u-reftable-block.c b/t/unit-tests/u-reftable-block.c
> > new file mode 100644
> > index 0000000000..af24901230
> > --- /dev/null
> > +++ b/t/unit-tests/u-reftable-block.c
> > @@ -0,0 +1,373 @@
> > +/*
> > +Copyright 2020 Google LLC
> > +
> > +Use of this source code is governed by a BSD-style
> > +license that can be found in the LICENSE file or at
> > +https://developers.google.com/open-source/licenses/bsd
> > +*/
> > +
> > +#include "unit-test.h"
> > +#include "reftable/block.h"
> > +#include "reftable/blocksource.h"
> > +#include "reftable/constants.h"
> > +#include "reftable/reftable-error.h"
> > +#include "strbuf.h"
> > +
> > +void test_reftable_block__index_read_write(void)
>
> This doesn't got to do anything with indices but with refs, so I'd
> rename this to `__ref_read_write()`.
>
> > +{
> > +     const int header_off = 21; /* random */
> > +     struct reftable_record recs[30];
> > +     const size_t N = ARRAY_SIZE(recs);
> > +     const size_t block_size = 1024;
> > +     struct reftable_block block = { 0 };
> > +     struct block_writer bw = {
> > +             .last_key = REFTABLE_BUF_INIT,
> > +     };
> > +     struct reftable_record rec = {
> > +             .type = BLOCK_TYPE_REF,
> > +     };
> > +     size_t i = 0;
> > +     int ret;
> > +     struct block_reader br = { 0 };
> > +     struct block_iter it = BLOCK_ITER_INIT;
> > +     struct reftable_buf want = REFTABLE_BUF_INIT, buf = REFTABLE_BUF_INIT;
> > +
> > +     REFTABLE_CALLOC_ARRAY(block.data, block_size);
> > +     cl_assert(block.data != NULL);
> > +     block.len = block_size;
> > +     block_source_from_buf(&block.source ,&buf);
> > +     ret = block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
> > +                             header_off, hash_size(REFTABLE_HASH_SHA1));
> > +     cl_assert(ret == 0);
>
> Same comment here, asserts like this can be retained as
> `cl_assert(!ret)`.
>

ohh, I didn't know this was possible.

> > +     rec.u.ref.refname = (char *) "";
> > +     rec.u.ref.value_type = REFTABLE_REF_DELETION;
> > +     ret = block_writer_add(&bw, &rec);
> > +     cl_assert_equal_i(ret, REFTABLE_API_ERROR);
> > +
> > +     for (i = 0; i < N; i++) {
> > +             rec.u.ref.refname = xstrfmt("branch%02"PRIuMAX, (uintmax_t)i);
> > +             rec.u.ref.value_type = REFTABLE_REF_VAL1;
> > +             memset(rec.u.ref.value.val1, i, REFTABLE_HASH_SIZE_SHA1);
> > +
> > +             recs[i] = rec;
> > +             ret = block_writer_add(&bw, &rec);
> > +             rec.u.ref.refname = NULL;
> > +             rec.u.ref.value_type = REFTABLE_REF_DELETION;
> > +             cl_assert_equal_i(ret, 0);
> > +     }
> > +
> > +     ret = block_writer_finish(&bw);
> > +     cl_assert(ret > 0);
>
> It's a bit unfortunate that we have to use `cl_assert()` here, but that
> isn't the fault of this series. I do have a pull request pending
> upstream that introduces integer comparisons. Once we've updated to that
> version I'll go through our unit tests and adapt callsites accordingly.
>
> [snip]
> > +void test_reftable_block__ref_read_write(void)
>
> This one here should be called `__index_read_write()`. I guess you
> confused the first and and this test name with one another.
>
> Patrick

Ah, okay, Thanks!

Best
Seyi

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar
  2025-05-02  9:57   ` Patrick Steinhardt
@ 2025-05-05  9:11     ` Seyi Chamber
  2025-05-05  9:52       ` Patrick Steinhardt
  0 siblings, 1 reply; 31+ messages in thread
From: Seyi Chamber @ 2025-05-05  9:11 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, phillip.wood

On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:
>
> On Tue, Apr 29, 2025 at 06:53:01PM +0100, Seyi Kuforiji wrote:
> > diff --git a/t/meson.build b/t/meson.build
> > index 756cb2a2dd..8fa00fc9ef 100644
> > --- a/t/meson.build
> > +++ b/t/meson.build
> > @@ -1102,7 +1102,6 @@ integration_tests = [
> >  # sufficient to catch missing test suites in our CI though.
> >  foreach glob, tests : {
> >    't[0-9][0-9][0-9][0-9]-*.sh': integration_tests,
> > -  'unit-tests/t-*.c': unit_test_programs,
> >    'unit-tests/u-*.c': clar_test_suites,
> >  }
> >    actual_tests = run_command(shell, '-c', 'ls ' + glob,
>
> Okay. Ideally this would be moved into the next commit where we remove
> the infrastructure for our old-style unit tests, but we can't because
> the glob matches nothing anymore and thus causes an error.
>
> Also, we have a "check-meson" target in "t/Makefile". Don't we have to
> remove unit tests from there, too?
>
> Patrick

You are referring to this, yes?
`@# awk acts up when trying to match single quotes, so we use \047 instead.
    @mkdir -p mesontmp && \
    printf "%s\n" \
        "integration_tests t[0-9][0-9][0-9][0-9]-*.sh" \
        "unit_test_programs unit-tests/t-*.c" \
        "clar_test_suites unit-tests/u-*.c" | \
    while read -r variable pattern; do \`

I've hardly taken a look at the `t/Makefile`, given my very little
interaction with the file throughout the test conversions.

Best
Seyi

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar
  2025-05-05  7:37     ` Seyi Chamber
@ 2025-05-05  9:52       ` Patrick Steinhardt
  2025-05-05 21:14         ` Junio C Hamano
  0 siblings, 1 reply; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-05  9:52 UTC (permalink / raw)
  To: Seyi Chamber; +Cc: git, phillip.wood

On Mon, May 05, 2025 at 08:37:27AM +0100, Seyi Chamber wrote:
> On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:
> >
> > On Tue, Apr 29, 2025 at 06:52:55PM +0100, Seyi Kuforiji wrote:
> > > diff --git a/t/unit-tests/t-reftable-block.c b/t/unit-tests/t-reftable-block.c
> > > deleted file mode 100644
> > > index 22040aeefa..0000000000
> > > --- a/t/unit-tests/t-reftable-block.c
> > > +++ /dev/null
> >
> > Hm, why is this recorded as a delete and creation? Weird, inspecting the
> > diff locally properly shows it as a rename, which makes it a ton easier
> > to review. It would be great if you could try to play around with the
> > `--find-renames` option in the next iteration of this series and double
> > check that these are shown as a rename.
> >
> 
> I used `--find-renames=90` for this patch series. Is there any
> recommended number to set it to? :/

Note that "90" also isn't interpreted the way you think it is, at least
going by git-format-patch(1):

    -M[<n>], --find-renames[=<n>]
        Detect renames. If <n> is specified, it is a threshold on the similarity index (i.e. amount
        of addition/deletions compared to the file’s size). For example, -M90% means Git should
        consider a delete/add pair to be a rename if more than 90% of the file hasn’t changed.
        Without a % sign, the number is to be read as a fraction, with a decimal point before it.
        I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly, -M05 is the same as -M5%.
        To limit detection to exact renames, use -M100%. The default similarity index is 50%.

What you probably wanted to say is `--find-renames=90%`, but without the
percentage sign it is read as a fraction, where 1 means "exact copy" and
0 means "all different".

I'd recommend to just play around with this option and inspect the
outcome until things look reviewable. :)

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 09/10] t/unit-tests: convert reftable stack test to use clar
  2025-05-05  9:11     ` Seyi Chamber
@ 2025-05-05  9:52       ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-05  9:52 UTC (permalink / raw)
  To: Seyi Chamber; +Cc: git, phillip.wood

On Mon, May 05, 2025 at 10:11:18AM +0100, Seyi Chamber wrote:
> On Fri, 2 May 2025 at 10:57, Patrick Steinhardt <ps@pks.im> wrote:
> >
> > On Tue, Apr 29, 2025 at 06:53:01PM +0100, Seyi Kuforiji wrote:
> > > diff --git a/t/meson.build b/t/meson.build
> > > index 756cb2a2dd..8fa00fc9ef 100644
> > > --- a/t/meson.build
> > > +++ b/t/meson.build
> > > @@ -1102,7 +1102,6 @@ integration_tests = [
> > >  # sufficient to catch missing test suites in our CI though.
> > >  foreach glob, tests : {
> > >    't[0-9][0-9][0-9][0-9]-*.sh': integration_tests,
> > > -  'unit-tests/t-*.c': unit_test_programs,
> > >    'unit-tests/u-*.c': clar_test_suites,
> > >  }
> > >    actual_tests = run_command(shell, '-c', 'ls ' + glob,
> >
> > Okay. Ideally this would be moved into the next commit where we remove
> > the infrastructure for our old-style unit tests, but we can't because
> > the glob matches nothing anymore and thus causes an error.
> >
> > Also, we have a "check-meson" target in "t/Makefile". Don't we have to
> > remove unit tests from there, too?
> >
> > Patrick
> 
> You are referring to this, yes?
> `@# awk acts up when trying to match single quotes, so we use \047 instead.
>     @mkdir -p mesontmp && \
>     printf "%s\n" \
>         "integration_tests t[0-9][0-9][0-9][0-9]-*.sh" \
>         "unit_test_programs unit-tests/t-*.c" \
>         "clar_test_suites unit-tests/u-*.c" | \
>     while read -r variable pattern; do \`
> 
> I've hardly taken a look at the `t/Makefile`, given my very little
> interaction with the file throughout the test conversions.

Yes, that's the part I was referring to.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
  2025-05-05  7:27     ` Seyi Chamber
@ 2025-05-05  9:52       ` Patrick Steinhardt
  2025-05-26  9:04         ` Seyi Chamber
  0 siblings, 1 reply; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-05  9:52 UTC (permalink / raw)
  To: Seyi Chamber; +Cc: git, phillip.wood

On Mon, May 05, 2025 at 08:27:16AM +0100, Seyi Chamber wrote:
> On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:
> > On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:
> > > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
> > > index e4c360fa7e..2958db5dc0 100644
> > > --- a/t/unit-tests/lib-reftable.h
> > > +++ b/t/unit-tests/lib-reftable.h
> > > @@ -6,12 +6,12 @@
> > >
> > >  struct reftable_buf;
> > >
> > > -void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > > +void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > >
> > > -struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
> > > +struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
> > >                                                struct reftable_write_options *opts);
> > >
> > > -void t_reftable_write_to_buf(struct reftable_buf *buf,
> > > +void cl_reftable_write_to_buf(struct reftable_buf *buf,
> > >                            struct reftable_ref_record *refs,
> > >                            size_t nrecords,
> > >                            struct reftable_log_record *logs,
> >
> > It is quite weird that we declare the replacement functions in
> > "unit-test.h" in the first commit only to remove them at a later point.
> > It would make way more sense if we introduced the functions in
> > "t/unit/lib-reftable.{c,h}" right from the start and then only remove
> > the unused functions in the last step.
> >
> > Patrick
> 
> If I get it correctly, you're suggesting I have both the original
> functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`

Yup, exactly.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar
  2025-05-05  9:52       ` Patrick Steinhardt
@ 2025-05-05 21:14         ` Junio C Hamano
  2025-05-06  5:10           ` Patrick Steinhardt
  0 siblings, 1 reply; 31+ messages in thread
From: Junio C Hamano @ 2025-05-05 21:14 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Seyi Chamber, git, phillip.wood

Patrick Steinhardt <ps@pks.im> writes:

>     -M[<n>], --find-renames[=<n>]
>         Detect renames. If <n> is specified, it is a threshold on the similarity index (i.e. amount
>         of addition/deletions compared to the file’s size). For example, -M90% means Git should
>         consider a delete/add pair to be a rename if more than 90% of the file hasn’t changed.
>         Without a % sign, the number is to be read as a fraction, with a decimal point before it.
>         I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly, -M05 is the same as -M5%.
>         To limit detection to exact renames, use -M100%. The default similarity index is 50%.
>
> What you probably wanted to say is `--find-renames=90%`, but without the
> percentage sign it is read as a fraction, where 1 means "exact copy" and
> 0 means "all different".

I am confused.  -M<number> without trailing %-sign is taken as
fraction against 1 followed by the same number of '0' has the
<number> has digits.  -M5 is 5 over 10, -M50 is 50 over 100.  -M90
is 90 over 100, so -M90 and -M90% should mean the same thing.

But you are right.  When you want to claim your pre- and post- image
files still correspond with each other in a meaningful way, even
after making extensive change, you would want to _lower_, not raise,
your similarity threshold.  If the default is -M50, then -M90 would
be a useful option to reject what Git (mistakenly) thinks are renames
and tell it to instead consider they are removals and creations.

THanks.

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar
  2025-05-05 21:14         ` Junio C Hamano
@ 2025-05-06  5:10           ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-06  5:10 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Seyi Chamber, git, phillip.wood

On Mon, May 05, 2025 at 02:14:39PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> >     -M[<n>], --find-renames[=<n>]
> >         Detect renames. If <n> is specified, it is a threshold on the similarity index (i.e. amount
> >         of addition/deletions compared to the file’s size). For example, -M90% means Git should
> >         consider a delete/add pair to be a rename if more than 90% of the file hasn’t changed.
> >         Without a % sign, the number is to be read as a fraction, with a decimal point before it.
> >         I.e., -M5 becomes 0.5, and is thus the same as -M50%. Similarly, -M05 is the same as -M5%.
> >         To limit detection to exact renames, use -M100%. The default similarity index is 50%.
> >
> > What you probably wanted to say is `--find-renames=90%`, but without the
> > percentage sign it is read as a fraction, where 1 means "exact copy" and
> > 0 means "all different".
> 
> I am confused.  -M<number> without trailing %-sign is taken as
> fraction against 1 followed by the same number of '0' has the
> <number> has digits.  -M5 is 5 over 10, -M50 is 50 over 100.  -M90
> is 90 over 100, so -M90 and -M90% should mean the same thing.

Hm, I guess _I_ was confused then. It doesn't feel natural to me that
-M90 would mean 90% whereas -M5 means 50%, and the documentation isn't
quite clear about this, either.

> But you are right.  When you want to claim your pre- and post- image
> files still correspond with each other in a meaningful way, even
> after making extensive change, you would want to _lower_, not raise,
> your similarity threshold.  If the default is -M50, then -M90 would
> be a useful option to reject what Git (mistakenly) thinks are renames
> and tell it to instead consider they are removals and creations.

Yeah, at least that point stands :)

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
  2025-05-05  9:52       ` Patrick Steinhardt
@ 2025-05-26  9:04         ` Seyi Chamber
  2025-05-26 12:56           ` Patrick Steinhardt
  0 siblings, 1 reply; 31+ messages in thread
From: Seyi Chamber @ 2025-05-26  9:04 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, phillip.wood

On Mon, 5 May 2025 at 10:52, Patrick Steinhardt <ps@pks.im> wrote:
>
> On Mon, May 05, 2025 at 08:27:16AM +0100, Seyi Chamber wrote:
> > On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:
> > > On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:
> > > > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
> > > > index e4c360fa7e..2958db5dc0 100644
> > > > --- a/t/unit-tests/lib-reftable.h
> > > > +++ b/t/unit-tests/lib-reftable.h
> > > > @@ -6,12 +6,12 @@
> > > >
> > > >  struct reftable_buf;
> > > >
> > > > -void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > > > +void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > > >
> > > > -struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
> > > > +struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
> > > >                                                struct reftable_write_options *opts);
> > > >
> > > > -void t_reftable_write_to_buf(struct reftable_buf *buf,
> > > > +void cl_reftable_write_to_buf(struct reftable_buf *buf,
> > > >                            struct reftable_ref_record *refs,
> > > >                            size_t nrecords,
> > > >                            struct reftable_log_record *logs,
> > >
> > > It is quite weird that we declare the replacement functions in
> > > "unit-test.h" in the first commit only to remove them at a later point.
> > > It would make way more sense if we introduced the functions in
> > > "t/unit/lib-reftable.{c,h}" right from the start and then only remove
> > > the unused functions in the last step.
> > >
> > > Patrick
> >
> > If I get it correctly, you're suggesting I have both the original
> > functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`
>
> Yup, exactly.
>
> Patrick

Hi Patrick,

Thanks for the suggestion to move both the original t-helpers and the
Clar-based cl_ versions into `t/unit/lib-reftable.{c,h}`.

I’ve tried doing that but ran into some build issues. The Clar-based
functions use clar_assert and clar_assert_equal, which aren’t
available to non-Clar tests. Since both sets of helpers would live in
the same object file, this causes linker errors for binaries that
don’t link against Clar.

I also hit Makefile warnings like “target 'lib-reftable.o' given more
than once,” due to the object being included in both Clar and non-Clar
test builds. And with all declarations in one header, non-Clar tests
see prototypes for Clar-only functions they can't link.

Also facing errors like these:
`LINK t/unit-tests/bin/t-reftable-basics
/usr/bin/ld: t/unit-tests/lib-reftable.o: in function
`cl_reftable_strbuf_writer':
/home/seyik/git/t/unit-tests/lib-reftable.c:113:(.text+0x520):
undefined reference to `clar__assert'`

What would you recommend I do to fix this?

Thanks,
Seyi

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar
  2025-05-26  9:04         ` Seyi Chamber
@ 2025-05-26 12:56           ` Patrick Steinhardt
  0 siblings, 0 replies; 31+ messages in thread
From: Patrick Steinhardt @ 2025-05-26 12:56 UTC (permalink / raw)
  To: Seyi Chamber; +Cc: git, phillip.wood

On Mon, May 26, 2025 at 10:04:43AM +0100, Seyi Chamber wrote:
> On Mon, 5 May 2025 at 10:52, Patrick Steinhardt <ps@pks.im> wrote:
> >
> > On Mon, May 05, 2025 at 08:27:16AM +0100, Seyi Chamber wrote:
> > > On Fri, 2 May 2025 at 10:58, Patrick Steinhardt <ps@pks.im> wrote:
> > > > On Tue, Apr 29, 2025 at 06:53:02PM +0100, Seyi Kuforiji wrote:
> > > > > diff --git a/t/unit-tests/lib-reftable.h b/t/unit-tests/lib-reftable.h
> > > > > index e4c360fa7e..2958db5dc0 100644
> > > > > --- a/t/unit-tests/lib-reftable.h
> > > > > +++ b/t/unit-tests/lib-reftable.h
> > > > > @@ -6,12 +6,12 @@
> > > > >
> > > > >  struct reftable_buf;
> > > > >
> > > > > -void t_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > > > > +void cl_reftable_set_hash(uint8_t *p, int i, enum reftable_hash id);
> > > > >
> > > > > -struct reftable_writer *t_reftable_strbuf_writer(struct reftable_buf *buf,
> > > > > +struct reftable_writer *cl_reftable_strbuf_writer(struct reftable_buf *buf,
> > > > >                                                struct reftable_write_options *opts);
> > > > >
> > > > > -void t_reftable_write_to_buf(struct reftable_buf *buf,
> > > > > +void cl_reftable_write_to_buf(struct reftable_buf *buf,
> > > > >                            struct reftable_ref_record *refs,
> > > > >                            size_t nrecords,
> > > > >                            struct reftable_log_record *logs,
> > > >
> > > > It is quite weird that we declare the replacement functions in
> > > > "unit-test.h" in the first commit only to remove them at a later point.
> > > > It would make way more sense if we introduced the functions in
> > > > "t/unit/lib-reftable.{c,h}" right from the start and then only remove
> > > > the unused functions in the last step.
> > > >
> > > > Patrick
> > >
> > > If I get it correctly, you're suggesting I have both the original
> > > functions and the clar-based variant in `t/unit/lib-reftable.{c,h}`
> >
> > Yup, exactly.
> >
> > Patrick
> 
> Hi Patrick,
> 
> Thanks for the suggestion to move both the original t-helpers and the
> Clar-based cl_ versions into `t/unit/lib-reftable.{c,h}`.
> 
> I’ve tried doing that but ran into some build issues. The Clar-based
> functions use clar_assert and clar_assert_equal, which aren’t
> available to non-Clar tests. Since both sets of helpers would live in
> the same object file, this causes linker errors for binaries that
> don’t link against Clar.
> 
> I also hit Makefile warnings like “target 'lib-reftable.o' given more
> than once,” due to the object being included in both Clar and non-Clar
> test builds. And with all declarations in one header, non-Clar tests
> see prototypes for Clar-only functions they can't link.
> 
> Also facing errors like these:
> `LINK t/unit-tests/bin/t-reftable-basics
> /usr/bin/ld: t/unit-tests/lib-reftable.o: in function
> `cl_reftable_strbuf_writer':
> /home/seyik/git/t/unit-tests/lib-reftable.c:113:(.text+0x520):
> undefined reference to `clar__assert'`
> 
> What would you recommend I do to fix this?

Hm. You could also have a separate "lib-reftable-clar.{c,h}" code unit
that contains the new definitions that you introduce early in your
series. At the end of the series you could then delete the old
"lib-reftable.{c,h}" and rename "lib-reftable-clar.{c,h}" accordingly to
have that name. It's requires a bit more shuffling, but given that the
final commit would then be a plain rename it shouldn't be too bad, I
guess.

Patrick

^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2025-05-26 12:56 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-29 17:52 [PATCH v2 00/10] t/unit-tests: convert unit-tests to use clar Seyi Kuforiji
2025-04-29 17:52 ` [PATCH v2 01/10] t/unit-tests: implement reftable test helper functions in unit-test.{c,h} Seyi Kuforiji
2025-04-29 23:04   ` Junio C Hamano
2025-05-02  9:57     ` Patrick Steinhardt
2025-05-02  9:57   ` Patrick Steinhardt
2025-04-29 17:52 ` [PATCH v2 02/10] t/unit-tests: convert reftable basics test to use clar test framework Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-04-29 17:52 ` [PATCH v2 03/10] t/unit-tests: convert reftable block test to use clar Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-05-05  7:37     ` Seyi Chamber
2025-05-05  9:52       ` Patrick Steinhardt
2025-05-05 21:14         ` Junio C Hamano
2025-05-06  5:10           ` Patrick Steinhardt
2025-04-29 17:52 ` [PATCH v2 04/10] t/unit-tests: convert reftable merged " Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-04-29 17:52 ` [PATCH v2 05/10] t/unit-tests: convert reftable pq " Seyi Kuforiji
2025-04-29 17:52 ` [PATCH v2 06/10] t/unit-tests: convert reftable reader " Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-04-29 17:52 ` [PATCH v2 07/10] t/unit-tests: convert reftable readwrite " Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-04-29 17:53 ` [PATCH v2 08/10] t/unit-tests: convert reftable record " Seyi Kuforiji
2025-04-29 17:53 ` [PATCH v2 09/10] t/unit-tests: convert reftable stack " Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-05-05  9:11     ` Seyi Chamber
2025-05-05  9:52       ` Patrick Steinhardt
2025-04-29 17:53 ` [PATCH v2 10/10] t/unit-tests: adapt lib-reftable{c,h} helper functions to clar Seyi Kuforiji
2025-05-02  9:57   ` Patrick Steinhardt
2025-05-05  7:27     ` Seyi Chamber
2025-05-05  9:52       ` Patrick Steinhardt
2025-05-26  9:04         ` Seyi Chamber
2025-05-26 12:56           ` Patrick Steinhardt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).