t/unit-tests: add tests for the in-memory object source

While the in-memory object source is a full-fledged source, our code base only exercises parts of its functionality because we only use it in git-blame(1). Implement unit tests to verify that the yet-unused functionality of the backend works as expected. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Apr 10, 2026 at 14:12 UTC d2902a45498793f8dc69abc6448f517b69437eec
3 files changed +315
Makefile
+1
@@ -1529,6 +1529,7 @@ CLAR_TEST_SUITES += u-hash
1529 CLAR_TEST_SUITES += u-hashmap
1530 CLAR_TEST_SUITES += u-list-objects-filter-options
1531 CLAR_TEST_SUITES += u-mem-pool
1532 +CLAR_TEST_SUITES += u-odb-inmemory
1533 CLAR_TEST_SUITES += u-oid-array
1534 CLAR_TEST_SUITES += u-oidmap
1535 CLAR_TEST_SUITES += u-oidtree
t/meson.build
+1
@@ -6,6 +6,7 @@ clar_test_suites = [
6 'unit-tests/u-hashmap.c',
7 'unit-tests/u-list-objects-filter-options.c',
8 'unit-tests/u-mem-pool.c',
9 + 'unit-tests/u-odb-inmemory.c',
10 'unit-tests/u-oid-array.c',
11 'unit-tests/u-oidmap.c',
12 'unit-tests/u-oidtree.c',
t/unit-tests/u-odb-inmemory.c new
+313
@@ -0,0 +1,313 @@
1 +#include "unit-test.h"
2 +#include "hex.h"
3 +#include "odb/source-inmemory.h"
4 +#include "odb/streaming.h"
5 +#include "oidset.h"
6 +#include "repository.h"
7 +#include "strbuf.h"
8 +
9 +#define RANDOM_OID "da39a3ee5e6b4b0d3255bfef95601890afd80709"
10 +#define FOOBAR_OID "f6ea0495187600e7b2288c8ac19c5886383a4632"
11 +
12 +static struct repository repo = {
13 + .hash_algo = &hash_algos[GIT_HASH_SHA1],
14 +};
15 +static struct object_database *odb;
16 +
17 +static void cl_assert_object_info(struct odb_source_inmemory *source,
18 + const struct object_id *oid,
19 + enum object_type expected_type,
20 + const char *expected_content)
21 +{
22 + enum object_type actual_type;
23 + unsigned long actual_size;
24 + void *actual_content;
25 + struct object_info oi = {
26 + .typep = &actual_type,
27 + .sizep = &actual_size,
28 + .contentp = &actual_content,
29 + };
30 +
31 + cl_must_pass(odb_source_read_object_info(&source->base, oid, &oi, 0));
32 + cl_assert_equal_u(actual_size, strlen(expected_content));
33 + cl_assert_equal_u(actual_type, expected_type);
34 + cl_assert_equal_s((char *) actual_content, expected_content);
35 +
36 + free(actual_content);
37 +}
38 +
39 +void test_odb_inmemory__initialize(void)
40 +{
41 + odb = odb_new(&repo, "", "");
42 +}
43 +
44 +void test_odb_inmemory__cleanup(void)
45 +{
46 + odb_free(odb);
47 +}
48 +
49 +void test_odb_inmemory__new(void)
50 +{
51 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
52 + cl_assert_equal_i(source->base.type, ODB_SOURCE_INMEMORY);
53 + odb_source_free(&source->base);
54 +}
55 +
56 +void test_odb_inmemory__read_missing_object(void)
57 +{
58 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
59 + struct object_id oid;
60 + const char *end;
61 +
62 + cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
63 + cl_must_fail(odb_source_read_object_info(&source->base, &oid, NULL, 0));
64 +
65 + odb_source_free(&source->base);
66 +}
67 +
68 +void test_odb_inmemory__read_empty_tree(void)
69 +{
70 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
71 + cl_assert_object_info(source, repo.hash_algo->empty_tree, OBJ_TREE, "");
72 + odb_source_free(&source->base);
73 +}
74 +
75 +void test_odb_inmemory__read_written_object(void)
76 +{
77 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
78 + const char data[] = "foobar";
79 + struct object_id written_oid;
80 +
81 + cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
82 + OBJ_BLOB, &written_oid, NULL, 0));
83 + cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
84 + cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
85 +
86 + odb_source_free(&source->base);
87 +}
88 +
89 +void test_odb_inmemory__read_stream_object(void)
90 +{
91 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
92 + struct odb_read_stream *stream;
93 + struct object_id written_oid;
94 + const char data[] = "foobar";
95 + char buf[3] = { 0 };
96 +
97 + cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
98 + OBJ_BLOB, &written_oid, NULL, 0));
99 +
100 + cl_must_pass(odb_source_read_object_stream(&stream, &source->base,
101 + &written_oid));
102 + cl_assert_equal_i(stream->type, OBJ_BLOB);
103 + cl_assert_equal_u(stream->size, 6);
104 +
105 + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2);
106 + cl_assert_equal_s(buf, "fo");
107 + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2);
108 + cl_assert_equal_s(buf, "ob");
109 + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 2);
110 + cl_assert_equal_s(buf, "ar");
111 + cl_assert_equal_i(odb_read_stream_read(stream, buf, 2), 0);
112 +
113 + odb_read_stream_close(stream);
114 + odb_source_free(&source->base);
115 +}
116 +
117 +static int add_one_object(const struct object_id *oid,
118 + struct object_info *oi UNUSED,
119 + void *payload)
120 +{
121 + struct oidset *actual_oids = payload;
122 + cl_must_pass(oidset_insert(actual_oids, oid));
123 + return 0;
124 +}
125 +
126 +void test_odb_inmemory__for_each_object(void)
127 +{
128 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
129 + struct odb_for_each_object_options opts = { 0 };
130 + struct oidset expected_oids = OIDSET_INIT;
131 + struct oidset actual_oids = OIDSET_INIT;
132 + struct strbuf buf = STRBUF_INIT;
133 +
134 + cl_must_pass(odb_source_for_each_object(&source->base, NULL,
135 + add_one_object, &actual_oids, &opts));
136 + cl_assert_equal_u(oidset_size(&actual_oids), 0);
137 +
138 + for (int i = 0; i < 10; i++) {
139 + struct object_id written_oid;
140 +
141 + strbuf_reset(&buf);
142 + strbuf_addf(&buf, "%d", i);
143 +
144 + cl_must_pass(odb_source_write_object(&source->base, buf.buf, buf.len,
145 + OBJ_BLOB, &written_oid, NULL, 0));
146 + cl_must_pass(oidset_insert(&expected_oids, &written_oid));
147 + }
148 +
149 + cl_must_pass(odb_source_for_each_object(&source->base, NULL,
150 + add_one_object, &actual_oids, &opts));
151 + cl_assert_equal_b(oidset_equal(&expected_oids, &actual_oids), true);
152 +
153 + odb_source_free(&source->base);
154 + oidset_clear(&expected_oids);
155 + oidset_clear(&actual_oids);
156 + strbuf_release(&buf);
157 +}
158 +
159 +static int abort_after_two_objects(const struct object_id *oid UNUSED,
160 + struct object_info *oi UNUSED,
161 + void *payload)
162 +{
163 + unsigned *counter = payload;
164 + (*counter)++;
165 + if (*counter == 2)
166 + return 123;
167 + return 0;
168 +}
169 +
170 +void test_odb_inmemory__for_each_object_can_abort_iteration(void)
171 +{
172 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
173 + struct odb_for_each_object_options opts = { 0 };
174 + struct object_id written_oid;
175 + unsigned counter = 0;
176 +
177 + cl_must_pass(odb_source_write_object(&source->base, "1", 1,
178 + OBJ_BLOB, &written_oid, NULL, 0));
179 + cl_must_pass(odb_source_write_object(&source->base, "2", 1,
180 + OBJ_BLOB, &written_oid, NULL, 0));
181 + cl_must_pass(odb_source_write_object(&source->base, "3", 1,
182 + OBJ_BLOB, &written_oid, NULL, 0));
183 +
184 + cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL,
185 + abort_after_two_objects,
186 + &counter, &opts),
187 + 123);
188 + cl_assert_equal_u(counter, 2);
189 +
190 + odb_source_free(&source->base);
191 +}
192 +
193 +void test_odb_inmemory__count_objects(void)
194 +{
195 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
196 + struct object_id written_oid;
197 + unsigned long count;
198 +
199 + cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
200 + cl_assert_equal_u(count, 0);
201 +
202 + cl_must_pass(odb_source_write_object(&source->base, "1", 1,
203 + OBJ_BLOB, &written_oid, NULL, 0));
204 + cl_must_pass(odb_source_write_object(&source->base, "2", 1,
205 + OBJ_BLOB, &written_oid, NULL, 0));
206 + cl_must_pass(odb_source_write_object(&source->base, "3", 1,
207 + OBJ_BLOB, &written_oid, NULL, 0));
208 +
209 + cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
210 + cl_assert_equal_u(count, 3);
211 +
212 + odb_source_free(&source->base);
213 +}
214 +
215 +void test_odb_inmemory__find_abbrev_len(void)
216 +{
217 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
218 + struct object_id oid1, oid2;
219 + unsigned abbrev_len;
220 +
221 + /*
222 + * The two blobs we're about to write share the first 10 hex characters
223 + * of their object IDs ("a09f43dc45"), so at least 11 characters are
224 + * needed to tell them apart:
225 + *
226 + * "368317" -> a09f43dc4562d45115583f5094640ae237df55f7
227 + * "514796" -> a09f43dc45fef837235eb7e6b1a6ca5e169a3981
228 + *
229 + * With only one blob written we expect a length of 4.
230 + */
231 + cl_must_pass(odb_source_write_object(&source->base, "368317", strlen("368317"),
232 + OBJ_BLOB, &oid1, NULL, 0));
233 + cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
234 + &abbrev_len));
235 + cl_assert_equal_u(abbrev_len, 4);
236 +
237 + /*
238 + * With both objects present, the shared 10-character prefix means we
239 + * need at least 11 characters to uniquely identify either object.
240 + */
241 + cl_must_pass(odb_source_write_object(&source->base, "514796", strlen("514796"),
242 + OBJ_BLOB, &oid2, NULL, 0));
243 + cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
244 + &abbrev_len));
245 + cl_assert_equal_u(abbrev_len, 11);
246 +
247 + odb_source_free(&source->base);
248 +}
249 +
250 +void test_odb_inmemory__freshen_object(void)
251 +{
252 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
253 + struct object_id written_oid;
254 + struct object_id oid;
255 + const char *end;
256 +
257 + cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
258 + cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0);
259 +
260 + cl_must_pass(odb_source_write_object(&source->base, "foobar",
261 + strlen("foobar"), OBJ_BLOB,
262 + &written_oid, NULL, 0));
263 + cl_assert_equal_i(odb_source_freshen_object(&source->base,
264 + &written_oid), 1);
265 +
266 + odb_source_free(&source->base);
267 +}
268 +
269 +struct membuf_write_stream {
270 + struct odb_write_stream base;
271 + const char *buf;
272 + size_t offset;
273 + size_t size;
274 +};
275 +
276 +static ssize_t membuf_write_stream_read(struct odb_write_stream *stream,
277 + unsigned char *buf, size_t len)
278 +{
279 + struct membuf_write_stream *s = container_of(stream, struct membuf_write_stream, base);
280 + size_t chunk_size = 2;
281 +
282 + if (chunk_size > len)
283 + chunk_size = len;
284 + if (chunk_size > s->size - s->offset)
285 + chunk_size = s->size - s->offset;
286 +
287 + memcpy(buf, s->buf + s->offset, chunk_size);
288 +
289 + s->offset += chunk_size;
290 + if (s->offset == s->size)
291 + s->base.is_finished = 1;
292 +
293 + return chunk_size;
294 +}
295 +
296 +void test_odb_inmemory__write_object_stream(void)
297 +{
298 + struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
299 + const char data[] = "foobar";
300 + struct membuf_write_stream stream = {
301 + .base.read = membuf_write_stream_read,
302 + .buf = data,
303 + .size = strlen(data),
304 + };
305 + struct object_id written_oid;
306 +
307 + cl_must_pass(odb_source_write_object_stream(&source->base, &stream.base,
308 + strlen(data), &written_oid));
309 + cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
310 + cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
311 +
312 + odb_source_free(&source->base);
313 +}