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
+}