feat(libxce-alloc-preason-equality): public alloc_eq/host_alloc_eq/job_alloc_eq with first-difference description (task 2.1)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-28 09:35:08 +00:00
parent 8f25c1c3f3
commit a6cbb323b0
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
6 changed files with 952 additions and 5 deletions

View file

@ -1,4 +1,8 @@
/* vasprintf() (GNU extension) builds the equality functions' difference
* descriptions. */
#define _GNU_SOURCE
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@ -454,3 +458,225 @@ int merge_job_alloc(xce_job_alloc_t* dst, const xce_job_alloc_t* src)
return merge_allocs(&(dst->nres_shared), &(dst->allocs_shared),
src->nres_shared, src->allocs_shared);
}
/* ------------------------------------------------------------------------
* Allocation equality (libxce-alloc-preason-equality). Contract in
* xcealloc.h. The description, when requested, is built bottom-up: the
* innermost comparison formats the differing field, and each enclosing list
* prefixes its own path segment and frees the inner string.
* ---------------------------------------------------------------------- */
/* *why = formatted string, or NULL if the allocation fails (vasprintf leaves
* its output undefined then). No-op when why is NULL. */
static void eq_why(char** why, const char* fmt, ...)
__attribute__((format(printf, 2, 3)));
static void eq_why(char** why, const char* fmt, ...)
{
va_list ap;
char* s = NULL;
if (why == NULL) {
return;
}
va_start(ap, fmt);
if (vasprintf(&s, fmt, ap) < 0) {
s = NULL;
}
va_end(ap);
*why = s;
}
/* Prefix an inner description with the list entry's path segment,
* `list[i](key=label).`; a failed inner description stays NULL. */
static void eq_why_wrap(char** why, const char* list, int i, const char* key,
const char* label)
{
char* inner;
if ((why == NULL) || (*why == NULL)) {
return;
}
inner = *why;
eq_why(why, "%s[%d](%s=%s).%s", list, i, key, label, inner);
free(inner);
}
static const char* eq_label(const char* a, const char* b)
{
if (a != NULL) {
return a;
}
return (b != NULL) ? b : "?";
}
static const char* eq_nullness(const void* p)
{
return (p != NULL) ? "non-NULL" : "NULL";
}
static int eq_str_field(const char* name, const char* a, const char* b,
char** why)
{
if (str_eq(a, b)) {
return 1;
}
eq_why(why, "%s: %s%s%s != %s%s%s", name, (a != NULL) ? "\"" : "",
(a != NULL) ? a : "NULL", (a != NULL) ? "\"" : "",
(b != NULL) ? "\"" : "", (b != NULL) ? b : "NULL",
(b != NULL) ? "\"" : "");
return 0;
}
/* In-order comparison of two `n`-entry xce_alloc_t lists named `list`. */
static int eq_alloc_list(const char* list, xce_alloc_t* const* a,
xce_alloc_t* const* b, int n, char** why)
{
int i;
for (i = 0; i < n; i++) {
if (a[i] == b[i]) {
continue;
}
if ((a[i] == NULL) || (b[i] == NULL)) {
eq_why(why, "%s[%d]: %s != %s", list, i, eq_nullness(a[i]),
eq_nullness(b[i]));
return 0;
}
if (!alloc_eq(a[i], b[i], why)) {
eq_why_wrap(why, list, i, "resname",
eq_label(a[i]->resname, b[i]->resname));
return 0;
}
}
return 1;
}
int alloc_eq(const xce_alloc_t* a, const xce_alloc_t* b, char** why)
{
if (why != NULL) {
*why = NULL;
}
if (a == b) {
return 1;
}
if ((a == NULL) || (b == NULL)) {
eq_why(why, "alloc: %s != %s", eq_nullness(a), eq_nullness(b));
return 0;
}
if (!eq_str_field("resname", a->resname, b->resname, why)
|| !eq_str_field("reserve", a->reserve, b->reserve, why)) {
return 0;
}
if (a->value != b->value) {
eq_why(why, "value: %.17g != %.17g", a->value, b->value);
return 0;
}
if (a->duration != b->duration) {
eq_why(why, "duration: %" PRId64 " != %" PRId64, a->duration,
b->duration);
return 0;
}
if (a->from != b->from) {
eq_why(why, "from: %" PRIu64 " != %" PRIu64, a->from, b->from);
return 0;
}
return 1;
}
int host_alloc_eq(const xce_host_alloc_t* a, const xce_host_alloc_t* b,
char** why)
{
if (why != NULL) {
*why = NULL;
}
if (a == b) {
return 1;
}
if ((a == NULL) || (b == NULL)) {
eq_why(why, "host_alloc: %s != %s", eq_nullness(a), eq_nullness(b));
return 0;
}
if (!eq_str_field("host", a->host, b->host, why)) {
return 0;
}
if (a->nallocs != b->nallocs) {
eq_why(why, "nallocs: %d != %d", a->nallocs, b->nallocs);
return 0;
}
return eq_alloc_list("allocs", a->allocs, b->allocs, a->nallocs, why);
}
static void eq_fmt_jobid(char* buf, size_t sz, uint64_t jobid)
{
if (fmt_jobid(buf, sz, jobid) < 0) {
snprintf(buf, sz, "%" PRIu64, jobid);
}
}
int job_alloc_eq(const xce_job_alloc_t* a, const xce_job_alloc_t* b,
char** why)
{
int i;
if (why != NULL) {
*why = NULL;
}
if (a == b) {
return 1;
}
if ((a == NULL) || (b == NULL)) {
eq_why(why, "job_alloc: %s != %s", eq_nullness(a), eq_nullness(b));
return 0;
}
if (a->jobid != b->jobid) {
if (why != NULL) {
char ja[128];
char jb[128];
eq_fmt_jobid(ja, sizeof(ja), a->jobid);
eq_fmt_jobid(jb, sizeof(jb), b->jobid);
eq_why(why, "jobid: %s != %s", ja, jb);
}
return 0;
}
if (a->nhosts != b->nhosts) {
eq_why(why, "nhosts: %d != %d", a->nhosts, b->nhosts);
return 0;
}
if (a->nres_shared != b->nres_shared) {
eq_why(why, "nres_shared: %d != %d", a->nres_shared, b->nres_shared);
return 0;
}
for (i = 0; i < a->nhosts; i++) {
const xce_host_alloc_t* ha = a->allocs[i];
const xce_host_alloc_t* hb = b->allocs[i];
if (ha == hb) {
continue;
}
if ((ha == NULL) || (hb == NULL)) {
eq_why(why, "allocs[%d]: %s != %s", i, eq_nullness(ha),
eq_nullness(hb));
return 0;
}
if (!host_alloc_eq(ha, hb, why)) {
eq_why_wrap(why, "allocs", i, "host", eq_label(ha->host, hb->host));
return 0;
}
}
return eq_alloc_list("allocs_shared", a->allocs_shared, b->allocs_shared,
a->nres_shared, why);
}