Files
Junio C Hamano 6e6f4b582c Merge branch 'ps/odb-generic-corrupt-objects'
The object database (odb) API has been refactored to distinguish
between missing objects and corrupt ones by returning more
descriptive error statuses.  Both the packed and loose backends now
faithfully propagate error details using a generic strbuf error
mechanism, removing backend-specific leakage from central lookup
paths.

* ps/odb-generic-corrupt-objects:
  odb: handle `OBJECT_INFO_DIE_IF_CORRUPT` generically
  odb/source: allow `read_object_info()` to bubble up error messages
  odb/source: let callers discern missing and corrupt objects
  odb/source: introduce error status when reading objects
  odb/source-packed: flag known-bad objects as corrupt and not missing
2026-08-31 08:24:59 -07:00

317 lines
8.9 KiB
C

#include "unit-test.h"
#include "hex.h"
#include "object-file.h"
#include "odb/source-inmemory.h"
#include "odb/streaming.h"
#include "oidset.h"
#include "repository.h"
#include "strbuf.h"
#define RANDOM_OID "da39a3ee5e6b4b0d3255bfef95601890afd80709"
#define FOOBAR_OID "f6ea0495187600e7b2288c8ac19c5886383a4632"
static struct repository repo = {
.hash_algo = &hash_algos[GIT_HASH_SHA1],
};
static struct object_database *odb;
static void cl_assert_object_info(struct odb_source_inmemory *source,
const struct object_id *oid,
enum object_type expected_type,
const char *expected_content)
{
enum object_type actual_type;
size_t actual_size;
void *actual_content;
struct object_info oi = {
.typep = &actual_type,
.sizep = &actual_size,
.contentp = &actual_content,
};
cl_must_pass(odb_source_read_object_info(&source->base, oid, &oi, 0, NULL));
cl_assert_equal_u(actual_size, strlen(expected_content));
cl_assert_equal_u(actual_type, expected_type);
cl_assert_equal_s((char *) actual_content, expected_content);
free(actual_content);
}
static void cl_assert_write_object(struct odb_source_inmemory *source,
const char *content,
enum object_type type,
struct object_id *oid)
{
size_t content_len = strlen(content);
hash_object_file(repo.hash_algo, content, content_len, type, oid);
cl_must_pass(odb_source_write_object(&source->base, content, content_len,
type, oid, NULL, NULL, 0));
}
void test_odb_inmemory__initialize(void)
{
odb = odb_new(&repo, 0);
}
void test_odb_inmemory__cleanup(void)
{
odb_free(odb);
}
void test_odb_inmemory__new(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
cl_assert_equal_i(source->base.type, ODB_SOURCE_INMEMORY);
odb_source_free(&source->base);
}
void test_odb_inmemory__read_missing_object(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct object_id oid;
const char *end;
cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
cl_assert_equal_i(odb_source_read_object_info(&source->base, &oid, NULL, 0, NULL),
ODB_READ_NOT_FOUND);
odb_source_free(&source->base);
}
void test_odb_inmemory__read_empty_tree(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
cl_assert_object_info(source, repo.hash_algo->empty_tree, OBJ_TREE, "");
odb_source_free(&source->base);
}
void test_odb_inmemory__read_written_object(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
const char data[] = "foobar";
struct object_id written_oid;
cl_assert_write_object(source, data, OBJ_BLOB, &written_oid);
cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
odb_source_free(&source->base);
}
void test_odb_inmemory__read_stream_object(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct odb_stream *stream;
struct object_id written_oid;
const char data[] = "foobar";
char buf[3] = { 0 };
cl_assert_write_object(source, data, OBJ_BLOB, &written_oid);
cl_must_pass(odb_source_read_object_stream(&stream, &source->base,
&written_oid));
cl_assert_equal_i(stream->type, OBJ_BLOB);
cl_assert_equal_u(stream->size, 6);
cl_assert_equal_i(odb_stream_read(stream, buf, 2), 2);
cl_assert_equal_s(buf, "fo");
cl_assert_equal_i(odb_stream_read(stream, buf, 2), 2);
cl_assert_equal_s(buf, "ob");
cl_assert_equal_i(odb_stream_read(stream, buf, 2), 2);
cl_assert_equal_s(buf, "ar");
cl_assert_equal_i(odb_stream_read(stream, buf, 2), 0);
odb_stream_close(stream);
odb_source_free(&source->base);
}
static int add_one_object(const struct object_id *oid,
struct object_info *oi UNUSED,
void *payload)
{
struct oidset *actual_oids = payload;
cl_must_pass(oidset_insert(actual_oids, oid));
return 0;
}
void test_odb_inmemory__for_each_object(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct odb_for_each_object_options opts = { 0 };
struct oidset expected_oids = OIDSET_INIT;
struct oidset actual_oids = OIDSET_INIT;
struct strbuf buf = STRBUF_INIT;
cl_must_pass(odb_source_for_each_object(&source->base, NULL,
add_one_object, &actual_oids, &opts));
cl_assert_equal_u(oidset_size(&actual_oids), 0);
for (int i = 0; i < 10; i++) {
struct object_id written_oid;
strbuf_reset(&buf);
strbuf_addf(&buf, "%d", i);
cl_assert_write_object(source, buf.buf, OBJ_BLOB, &written_oid);
cl_must_pass(oidset_insert(&expected_oids, &written_oid));
}
cl_must_pass(odb_source_for_each_object(&source->base, NULL,
add_one_object, &actual_oids, &opts));
cl_assert_equal_b(oidset_equal(&expected_oids, &actual_oids), true);
odb_source_free(&source->base);
oidset_clear(&expected_oids);
oidset_clear(&actual_oids);
strbuf_release(&buf);
}
static int abort_after_two_objects(const struct object_id *oid UNUSED,
struct object_info *oi UNUSED,
void *payload)
{
unsigned *counter = payload;
(*counter)++;
if (*counter == 2)
return 123;
return 0;
}
void test_odb_inmemory__for_each_object_can_abort_iteration(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct odb_for_each_object_options opts = { 0 };
struct object_id written_oid;
unsigned counter = 0;
cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid);
cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid);
cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid);
cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL,
abort_after_two_objects,
&counter, &opts),
123);
cl_assert_equal_u(counter, 2);
odb_source_free(&source->base);
}
void test_odb_inmemory__count_objects(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct object_id written_oid;
unsigned long count;
cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
cl_assert_equal_u(count, 0);
cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid);
cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid);
cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid);
cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
cl_assert_equal_u(count, 3);
odb_source_free(&source->base);
}
void test_odb_inmemory__find_abbrev_len(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct object_id oid1, oid2;
unsigned abbrev_len;
/*
* The two blobs we're about to write share the first 10 hex characters
* of their object IDs ("a09f43dc45"), so at least 11 characters are
* needed to tell them apart:
*
* "368317" -> a09f43dc4562d45115583f5094640ae237df55f7
* "514796" -> a09f43dc45fef837235eb7e6b1a6ca5e169a3981
*
* With only one blob written we expect a length of 4.
*/
cl_assert_write_object(source, "368317", OBJ_BLOB, &oid1);
cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
&abbrev_len));
cl_assert_equal_u(abbrev_len, 4);
/*
* With both objects present, the shared 10-character prefix means we
* need at least 11 characters to uniquely identify either object.
*/
cl_assert_write_object(source, "514796", OBJ_BLOB, &oid2);
cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
&abbrev_len));
cl_assert_equal_u(abbrev_len, 11);
odb_source_free(&source->base);
}
void test_odb_inmemory__freshen_object(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
struct object_id written_oid;
struct object_id oid;
const char *end;
cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid, NULL), 0);
cl_assert_write_object(source, "foobar", OBJ_BLOB, &written_oid);
cl_assert_equal_i(odb_source_freshen_object(&source->base,
&written_oid, NULL), 1);
odb_source_free(&source->base);
}
struct membuf_write_stream {
struct odb_stream base;
const char *buf;
size_t offset;
};
static ssize_t membuf_write_stream_read(struct odb_stream *stream,
char *buf, size_t len)
{
struct membuf_write_stream *s = container_of(stream, struct membuf_write_stream, base);
size_t chunk_size = 2;
if (s->offset == s->base.size)
return 0;
if (chunk_size > len)
chunk_size = len;
if (chunk_size > s->base.size - s->offset)
chunk_size = s->base.size - s->offset;
memcpy(buf, s->buf + s->offset, chunk_size);
s->offset += chunk_size;
return chunk_size;
}
void test_odb_inmemory__write_object_stream(void)
{
struct odb_source_inmemory *source = odb_source_inmemory_new(odb);
const char data[] = "foobar";
struct membuf_write_stream stream = {
.base = {
.read = membuf_write_stream_read,
.size = strlen(data),
.type = OBJ_BLOB,
},
.buf = data,
};
struct object_id written_oid;
cl_must_pass(odb_source_write_object_stream(&source->base, &stream.base,
&written_oid));
cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
odb_source_free(&source->base);
}