From 62d0b8b6865c8e67d07d1751f6fb2f4f20f2b305 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Mon, 28 Sep 2026 09:49:57 +0000 Subject: [PATCH] feat(libxce-alloc-preason-equality): full-match job_preason_eq with first-difference description (task 2.2) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../libxce-alloc-preason-equality/tasks.md | 4 +- src/include/preason.h | 41 ++ src/lib/preason.c | 175 +++++++++ src/lib/testsuite/test_preason.c | 355 ++++++++++++++++++ src/lib/testsuite/test_xcealloc_oom.c | 75 ++++ 5 files changed, 648 insertions(+), 2 deletions(-) diff --git a/.kiro/specs/libxce-alloc-preason-equality/tasks.md b/.kiro/specs/libxce-alloc-preason-equality/tasks.md index 383af532..bc824487 100644 --- a/.kiro/specs/libxce-alloc-preason-equality/tasks.md +++ b/.kiro/specs/libxce-alloc-preason-equality/tasks.md @@ -9,7 +9,7 @@ Test-first throughout (red → green → refactor). Every sub-task leaves the bu - Done when the libxce utility tests include these cases and the libxce suite passes under ASAN. - _Requirements: 1.5, 2.3_ -- [ ] 2. Core: public equality API in libxce +- [x] 2. Core: public equality API in libxce - [x] 2.1 Allocation equality for single records, host allocations and job allocations, with an optional first-difference description - Write the tests first: a match; a mismatch on each compared field (resource name, reserve, value, duration, reservation start time) with the expected description; an allocation-instance-id difference still compares equal; host name, entry order and list length matter; job id, ordered hosts, ordered shared resources and list-length mismatches; NULL handling at the top level, in list entries and in string fields; no description requested; no description returned when equal. - Add a wrapped description-allocation failure to the existing allocation OOM test binary (bug-1882) and test that a failed description leaves no description and the same result. @@ -19,7 +19,7 @@ Test-first throughout (red → green → refactor). Every sub-task leaves the bu - Done when the new tests pass, the existing allocation and allocation-OOM tests still pass, and every test frees the descriptions it receives (clean under ASAN/LeakSanitizer). - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 6.4, 6.6_ - _Boundary: libxce allocation module_ -- [ ] 2.2 Full-match pending-reason equality with an optional first-difference description +- [x] 2.2 Full-match pending-reason equality with an optional first-difference description - Write the tests first: a match; main-reason mismatch; host-based reason list mismatch per field, order and length; resource-based reason list mismatch per field, order and length; NULL handling at the top level, in list entries and in string fields; each with the expected description; no description requested; no description returned when equal; a failed description allocation (in the allocation OOM test binary, which already wraps the formatter after 2.1) leaves no description and the same result. - Implement the full structural match in the pending-reason module per design.md (Components: Pending-reason equality, Decision 3), enabling the GNU extension the description formatter needs. - Document in the public header the full-match contract, NULL/order semantics, the malformed-record precondition, the description ownership, and that the simulator deliberately uses a main-reason-only check instead (bug-1845). diff --git a/src/include/preason.h b/src/include/preason.h index 470451a7..042c4ab0 100644 --- a/src/include/preason.h +++ b/src/include/preason.h @@ -12,4 +12,45 @@ extern void erase_job_preason(xce_job_preason_t*); extern int add_job_preason(xce_job_preason_t*, int, char*, char*); extern xce_pendreason_t* make_pendreason(uint64_t); +/* + * Pending-reason equality: "are these two pending-reason records identical". + * + * job_preason_eq() is a FULL structural match. It compares, in this order: + * main_reason; nhost_reasons, then host_based_reasons[i] in order, each by + * host then reason; nres_reasons, then res_based_reasons[i] in order, each + * by resource, host, then reason. Nothing is excluded. Lists are + * order-sensitive: the same entries in a different order are not equal. + * + * The scheduler simulator (--sim-verify, sim_verify.c) deliberately does NOT + * use this function for its PEND outcome check: it compares main_reason only + * (bug-1845), because a recorded alloc-cache-hit PEND carries only the main + * reason while the simulator (which has no alloc cache) always builds the + * full detail lists. That loosening is a replay property, not object + * identity, so it stays simulator-local. + * + * NULL handling: identical pointers (including two NULLs) are equal; exactly + * one NULL argument, list entry or string field is not equal. Precondition: + * a non-zero count with a NULL entry array is a malformed record and is not + * handled. Neither argument is modified. + * + * `why` is optional. When NULL, nothing is allocated. Otherwise *why is set + * to NULL when the arguments are equal, or to a newly allocated, + * NUL-terminated description of the first difference found, in comparison + * order, e.g. + * main_reason: 5 != 6 + * nhost_reasons: 2 != 1 + * host_based_reasons[1](host=h2).host: "h2" != "h9" + * res_based_reasons[0](resource=mem).reason: 3 != 8 + * res_based_reasons[0]: non-NULL != NULL + * job_preason: NULL != non-NULL (a NULL top-level argument) + * Path labels come from `a`, from `b` when a's string is NULL, or are `?`. + * The caller owns the description and releases it with free(). The value + * *why holds on entry is never read or freed. If allocating the description + * fails, *why is NULL and the result is unchanged. Nothing is logged. + * + * Returns non-zero when equal, 0 when not. + */ +extern int job_preason_eq(const xce_job_preason_t* a, + const xce_job_preason_t* b, char** why); + #endif diff --git a/src/lib/preason.c b/src/lib/preason.c index 6baefd27..13088a63 100644 --- a/src/lib/preason.c +++ b/src/lib/preason.c @@ -1,5 +1,11 @@ +/* vasprintf() (GNU extension) builds job_preason_eq()'s difference + * description. */ +#define _GNU_SOURCE #include +#include +#include #include +#include #include "preason.h" #include "utils.h" @@ -175,3 +181,172 @@ xce_pendreason_t *make_pendreason(uint64_t jobid) { return reason; } + +/* ------------------------------------------------------------------------ + * Pending-reason equality (libxce-alloc-preason-equality). Contract in + * preason.h. Same description conventions as xcealloc.c's alloc_eq family; + * the small formatting helpers are duplicated here (static) rather than + * exported, to keep the module boundaries of the design. + * ---------------------------------------------------------------------- */ + +/* *why = formatted string, or NULL if the allocation fails (vasprintf leaves + * its output undefined then). No-op when why is NULL. */ +static void preason_why(char** why, const char* fmt, ...) + __attribute__((format(printf, 2, 3))); + +static void preason_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; +} + +static const char* preason_label(const char* a, const char* b) +{ + if (a != NULL) { + return a; + } + return (b != NULL) ? b : "?"; +} + +static const char* preason_nullness(const void* p) +{ + return (p != NULL) ? "non-NULL" : "NULL"; +} + +/* Compare string field `field` of entry `list[i](key=label)`. */ +static int preason_str_field(const char* list, int i, const char* key, + const char* label, const char* field, + const char* a, const char* b, char** why) +{ + if (str_eq(a, b)) { + return 1; + } + + preason_why(why, "%s[%d](%s=%s).%s: %s%s%s != %s%s%s", list, i, key, + label, field, (a != NULL) ? "\"" : "", + (a != NULL) ? a : "NULL", (a != NULL) ? "\"" : "", + (b != NULL) ? "\"" : "", (b != NULL) ? b : "NULL", + (b != NULL) ? "\"" : ""); + return 0; +} + +static int host_reasons_eq(const xce_job_preason_t* a, + const xce_job_preason_t* b, char** why) +{ + static const char* list = "host_based_reasons"; + int i; + + for (i = 0; i < a->nhost_reasons; i++) { + const xce_host_reason_t* ra = a->host_based_reasons[i]; + const xce_host_reason_t* rb = b->host_based_reasons[i]; + const char* label; + + if (ra == rb) { + continue; + } + if ((ra == NULL) || (rb == NULL)) { + preason_why(why, "%s[%d]: %s != %s", list, i, + preason_nullness(ra), preason_nullness(rb)); + return 0; + } + + label = preason_label(ra->host, rb->host); + if (!preason_str_field(list, i, "host", label, "host", ra->host, + rb->host, why)) { + return 0; + } + if (ra->reason != rb->reason) { + preason_why(why, "%s[%d](host=%s).reason: %d != %d", list, i, + label, ra->reason, rb->reason); + return 0; + } + } + + return 1; +} + +static int res_reasons_eq(const xce_job_preason_t* a, + const xce_job_preason_t* b, char** why) +{ + static const char* list = "res_based_reasons"; + int i; + + for (i = 0; i < a->nres_reasons; i++) { + const xce_res_reason_t* ra = a->res_based_reasons[i]; + const xce_res_reason_t* rb = b->res_based_reasons[i]; + const char* label; + + if (ra == rb) { + continue; + } + if ((ra == NULL) || (rb == NULL)) { + preason_why(why, "%s[%d]: %s != %s", list, i, + preason_nullness(ra), preason_nullness(rb)); + return 0; + } + + label = preason_label(ra->resource, rb->resource); + if (!preason_str_field(list, i, "resource", label, "resource", + ra->resource, rb->resource, why) + || !preason_str_field(list, i, "resource", label, "host", + ra->host, rb->host, why)) { + return 0; + } + if (ra->reason != rb->reason) { + preason_why(why, "%s[%d](resource=%s).reason: %d != %d", list, i, + label, ra->reason, rb->reason); + return 0; + } + } + + return 1; +} + +int job_preason_eq(const xce_job_preason_t* a, const xce_job_preason_t* b, + char** why) +{ + if (why != NULL) { + *why = NULL; + } + if (a == b) { + return 1; + } + if ((a == NULL) || (b == NULL)) { + preason_why(why, "job_preason: %s != %s", preason_nullness(a), + preason_nullness(b)); + return 0; + } + + if (a->main_reason != b->main_reason) { + preason_why(why, "main_reason: %d != %d", a->main_reason, + b->main_reason); + return 0; + } + if (a->nhost_reasons != b->nhost_reasons) { + preason_why(why, "nhost_reasons: %d != %d", a->nhost_reasons, + b->nhost_reasons); + return 0; + } + if (!host_reasons_eq(a, b, why)) { + return 0; + } + if (a->nres_reasons != b->nres_reasons) { + preason_why(why, "nres_reasons: %d != %d", a->nres_reasons, + b->nres_reasons); + return 0; + } + + return res_reasons_eq(a, b, why); +} diff --git a/src/lib/testsuite/test_preason.c b/src/lib/testsuite/test_preason.c index 99a7fa6a..3ad6a035 100644 --- a/src/lib/testsuite/test_preason.c +++ b/src/lib/testsuite/test_preason.c @@ -4,6 +4,7 @@ #include "preason.h" #include "unity.h" +#include "utils.h" static void test_main_reason() { @@ -175,6 +176,348 @@ static void test_add_more_res_reason(void) erase_job_preason(&reason); } +/* ------------------------------------------------------------------------ + * job_preason_eq (libxce-alloc-preason-equality task 2.2, Req 2.1-2.3, + * 3.1-3.6, 6.4): full structural match with a first-difference description. + * ---------------------------------------------------------------------- */ + +#define ASSERT_PREASON_NE_WHY(a, b, expect) \ + do { \ + char* why_ = NULL; \ + TEST_ASSERT_FALSE(job_preason_eq((a), (b), &why_)); \ + TEST_ASSERT_NOT_NULL(why_); \ + TEST_ASSERT_EQUAL_STRING((expect), why_); \ + free(why_); \ + TEST_ASSERT_FALSE(job_preason_eq((a), (b), NULL)); \ + } while (0) + +/* main_reason 5; host reasons h1:1, h2:2; resource reasons (mem,h1):3, + * (cpu,h2):4. add_job_preason() with a main reason erases the lists, so the + * main reason is set directly. */ +static void build_full(xce_job_preason_t* r) +{ + memset(r, 0x0, sizeof(*r)); + TEST_ASSERT_EQUAL_INT(1, add_job_preason(r, 1, "h1", NULL)); + TEST_ASSERT_EQUAL_INT(2, add_job_preason(r, 2, "h2", NULL)); + TEST_ASSERT_EQUAL_INT(1, add_job_preason(r, 3, "h1", "mem")); + TEST_ASSERT_EQUAL_INT(2, add_job_preason(r, 4, "h2", "cpu")); + r->main_reason = 5; +} + +static void test_preason_eq_match_sets_why_null(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + char dummy; + char* why = &dummy; /* never read or freed on entry (Req 3.4) */ + + build_full(&a); + build_full(&b); + + TEST_ASSERT_TRUE(job_preason_eq(&a, &b, &why)); + TEST_ASSERT_NULL(why); + TEST_ASSERT_TRUE(job_preason_eq(&a, &b, NULL)); + TEST_ASSERT_TRUE(job_preason_eq(&a, &a, &why)); + TEST_ASSERT_NULL(why); + + erase_job_preason(&a); + erase_job_preason(&b); +} + +static void test_preason_eq_empty_lists_match(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + char* why = NULL; + + memset(&a, 0x0, sizeof(a)); + memset(&b, 0x0, sizeof(b)); + add_job_preason(&a, 7, NULL, NULL); + add_job_preason(&b, 7, NULL, NULL); + + TEST_ASSERT_TRUE(job_preason_eq(&a, &b, &why)); + TEST_ASSERT_NULL(why); +} + +static void test_preason_eq_main_reason_mismatch(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + + build_full(&a); + build_full(&b); + b.main_reason = 6; + + ASSERT_PREASON_NE_WHY(&a, &b, "main_reason: 5 != 6"); + + erase_job_preason(&a); + erase_job_preason(&b); +} + +/* Main reason equal but detail differs: the full match must see it (this is + * what the simulator's main-reason-only check deliberately ignores). */ +static void test_preason_eq_detail_matters_when_main_equal(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + + build_full(&a); + memset(&b, 0x0, sizeof(b)); + b.main_reason = 5; + + ASSERT_PREASON_NE_WHY(&a, &b, "nhost_reasons: 2 != 0"); + + erase_job_preason(&a); +} + +static void test_preason_eq_host_reason_field_mismatch(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + + /* host name */ + build_full(&a); + build_full(&b); + free(b.host_based_reasons[1]->host); + b.host_based_reasons[1]->host = strdup("h9"); + ASSERT_PREASON_NE_WHY(&a, &b, + "host_based_reasons[1](host=h2).host: \"h2\" != " + "\"h9\""); + erase_job_preason(&b); + + /* reason code */ + build_full(&b); + b.host_based_reasons[0]->reason = 9; + ASSERT_PREASON_NE_WHY(&a, &b, + "host_based_reasons[0](host=h1).reason: 1 != 9"); + erase_job_preason(&b); + + erase_job_preason(&a); +} + +static void test_preason_eq_host_reason_order_and_length(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + + /* same entries, other order */ + build_full(&a); + memset(&b, 0x0, sizeof(b)); + add_job_preason(&b, 2, "h2", NULL); + add_job_preason(&b, 1, "h1", NULL); + add_job_preason(&b, 3, "h1", "mem"); + add_job_preason(&b, 4, "h2", "cpu"); + b.main_reason = 5; + ASSERT_PREASON_NE_WHY(&a, &b, + "host_based_reasons[0](host=h1).host: \"h1\" != " + "\"h2\""); + erase_job_preason(&b); + + /* one entry fewer */ + memset(&b, 0x0, sizeof(b)); + add_job_preason(&b, 1, "h1", NULL); + add_job_preason(&b, 3, "h1", "mem"); + add_job_preason(&b, 4, "h2", "cpu"); + b.main_reason = 5; + ASSERT_PREASON_NE_WHY(&a, &b, "nhost_reasons: 2 != 1"); + erase_job_preason(&b); + + erase_job_preason(&a); +} + +static void test_preason_eq_res_reason_field_mismatch(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + + /* resource name */ + build_full(&a); + build_full(&b); + free(b.res_based_reasons[0]->resource); + b.res_based_reasons[0]->resource = strdup("gpu"); + ASSERT_PREASON_NE_WHY(&a, &b, + "res_based_reasons[0](resource=mem).resource: " + "\"mem\" != \"gpu\""); + erase_job_preason(&b); + + /* host name */ + build_full(&b); + free(b.res_based_reasons[1]->host); + b.res_based_reasons[1]->host = strdup("h1"); + ASSERT_PREASON_NE_WHY(&a, &b, + "res_based_reasons[1](resource=cpu).host: \"h2\" " + "!= \"h1\""); + erase_job_preason(&b); + + /* reason code */ + build_full(&b); + b.res_based_reasons[1]->reason = 8; + ASSERT_PREASON_NE_WHY(&a, &b, + "res_based_reasons[1](resource=cpu).reason: 4 != 8"); + erase_job_preason(&b); + + erase_job_preason(&a); +} + +static void test_preason_eq_res_reason_order_and_length(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + + /* same entries, other order */ + build_full(&a); + memset(&b, 0x0, sizeof(b)); + add_job_preason(&b, 1, "h1", NULL); + add_job_preason(&b, 2, "h2", NULL); + add_job_preason(&b, 4, "h2", "cpu"); + add_job_preason(&b, 3, "h1", "mem"); + b.main_reason = 5; + ASSERT_PREASON_NE_WHY(&a, &b, + "res_based_reasons[0](resource=mem).resource: " + "\"mem\" != \"cpu\""); + erase_job_preason(&b); + + /* one entry more */ + build_full(&b); + add_job_preason(&b, 5, "h3", "disk"); + ASSERT_PREASON_NE_WHY(&a, &b, "nres_reasons: 2 != 3"); + erase_job_preason(&b); + + erase_job_preason(&a); +} + +static void test_preason_eq_null_top_level(void) +{ + xce_job_preason_t a; + char* why = NULL; + + build_full(&a); + + TEST_ASSERT_TRUE(job_preason_eq(NULL, NULL, &why)); + TEST_ASSERT_NULL(why); + ASSERT_PREASON_NE_WHY(&a, NULL, "job_preason: non-NULL != NULL"); + ASSERT_PREASON_NE_WHY(NULL, &a, "job_preason: NULL != non-NULL"); + + erase_job_preason(&a); +} + +static void test_preason_eq_null_list_entries(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + xce_job_preason_t c; + + build_full(&a); + build_full(&b); + build_full(&c); + + /* host entry NULL on one side */ + free(b.host_based_reasons[1]->host); + free(b.host_based_reasons[1]); + b.host_based_reasons[1] = NULL; + ASSERT_PREASON_NE_WHY(&a, &b, "host_based_reasons[1]: non-NULL != NULL"); + ASSERT_PREASON_NE_WHY(&b, &a, "host_based_reasons[1]: NULL != non-NULL"); + + /* resource entry NULL on one side */ + free(c.res_based_reasons[0]->host); + free(c.res_based_reasons[0]->resource); + free(c.res_based_reasons[0]); + c.res_based_reasons[0] = NULL; + ASSERT_PREASON_NE_WHY(&a, &c, "res_based_reasons[0]: non-NULL != NULL"); + ASSERT_PREASON_NE_WHY(&c, &a, "res_based_reasons[0]: NULL != non-NULL"); + + /* NULL entries at the same index on both sides are equal */ + { + xce_job_preason_t d; + char* why = NULL; + + build_full(&d); + free(d.host_based_reasons[1]->host); + free(d.host_based_reasons[1]); + d.host_based_reasons[1] = NULL; + TEST_ASSERT_TRUE(job_preason_eq(&b, &d, &why)); + TEST_ASSERT_NULL(why); + erase_job_preason(&d); + } + + erase_job_preason(&a); + erase_job_preason(&b); + erase_job_preason(&c); +} + +static void test_preason_eq_null_string_fields(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + char* why = NULL; + + build_full(&a); + build_full(&b); + + /* host reason host NULL in a: label taken from b */ + FREEUP(a.host_based_reasons[0]->host); + ASSERT_PREASON_NE_WHY(&a, &b, + "host_based_reasons[0](host=h1).host: NULL != " + "\"h1\""); + /* both NULL: the field compares equal, so there is no description */ + FREEUP(b.host_based_reasons[0]->host); + TEST_ASSERT_TRUE(job_preason_eq(&a, &b, &why)); + TEST_ASSERT_NULL(why); + /* both hosts NULL, reason differs: label is `?` */ + b.host_based_reasons[0]->reason = 7; + ASSERT_PREASON_NE_WHY(&a, &b, + "host_based_reasons[0](host=?).reason: 1 != 7"); + erase_job_preason(&a); + erase_job_preason(&b); + + /* resource reason resource NULL in b: label from a */ + build_full(&a); + build_full(&b); + FREEUP(b.res_based_reasons[1]->resource); + ASSERT_PREASON_NE_WHY(&a, &b, + "res_based_reasons[1](resource=cpu).resource: " + "\"cpu\" != NULL"); + /* resource NULL in a: label from b */ + ASSERT_PREASON_NE_WHY(&b, &a, + "res_based_reasons[1](resource=cpu).resource: NULL " + "!= \"cpu\""); + erase_job_preason(&b); + + /* resource reason host NULL */ + build_full(&b); + FREEUP(b.res_based_reasons[0]->host); + ASSERT_PREASON_NE_WHY(&a, &b, + "res_based_reasons[0](resource=mem).host: \"h1\" != " + "NULL"); + erase_job_preason(&b); + + erase_job_preason(&a); +} + +/* The comparison never modifies its arguments (Req 2.3). */ +static void test_preason_eq_does_not_modify_arguments(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + xce_job_preason_t a0; + char* why = NULL; + + build_full(&a); + build_full(&b); + b.res_based_reasons[1]->reason = 8; + a0 = a; + + TEST_ASSERT_FALSE(job_preason_eq(&a, &b, &why)); + free(why); + TEST_ASSERT_EQUAL_MEMORY(&a0, &a, sizeof(a)); + TEST_ASSERT_EQUAL_STRING("h1", a.host_based_reasons[0]->host); + TEST_ASSERT_EQUAL_STRING("mem", a.res_based_reasons[0]->resource); + TEST_ASSERT_EQUAL_INT(8, b.res_based_reasons[1]->reason); + + erase_job_preason(&a); + erase_job_preason(&b); +} + void test_preason(void) { printf("\n>> preason test\n\n"); @@ -187,4 +530,16 @@ void test_preason(void) RUN_TEST(test_add_main_reset_other); RUN_TEST(test_add_more_host_reason); RUN_TEST(test_add_more_res_reason); + RUN_TEST(test_preason_eq_match_sets_why_null); + RUN_TEST(test_preason_eq_empty_lists_match); + RUN_TEST(test_preason_eq_main_reason_mismatch); + RUN_TEST(test_preason_eq_detail_matters_when_main_equal); + RUN_TEST(test_preason_eq_host_reason_field_mismatch); + RUN_TEST(test_preason_eq_host_reason_order_and_length); + RUN_TEST(test_preason_eq_res_reason_field_mismatch); + RUN_TEST(test_preason_eq_res_reason_order_and_length); + RUN_TEST(test_preason_eq_null_top_level); + RUN_TEST(test_preason_eq_null_list_entries); + RUN_TEST(test_preason_eq_null_string_fields); + RUN_TEST(test_preason_eq_does_not_modify_arguments); } diff --git a/src/lib/testsuite/test_xcealloc_oom.c b/src/lib/testsuite/test_xcealloc_oom.c index e7ccabdb..3e9eb564 100644 --- a/src/lib/testsuite/test_xcealloc_oom.c +++ b/src/lib/testsuite/test_xcealloc_oom.c @@ -16,6 +16,8 @@ * wraps vasprintf() (and __vasprintf_chk(), which a _FORTIFY_SOURCE build * calls instead) so a test can make the equality functions' description * allocation fail: the result must be unchanged and *why left NULL. + * Task 2.2 uses the same wrap for job_preason_eq() (preason.c, also in the + * static libxce). */ #include #include @@ -23,6 +25,7 @@ #include #include +#include "preason.h" #include "unity.h" #include "xce/types.h" #include "xcealloc.h" @@ -309,6 +312,77 @@ static void test_job_alloc_eq_description_oom_at_every_level(void) free_JobAlloc_ptr(&b); } +/* Req 3.5 for job_preason_eq(): a failed description allocation leaves *why + * NULL and the result unchanged, for a top-level, main-reason, list-length + * and nested-entry difference alike. */ +static void test_job_preason_eq_description_oom_leaves_why_null(void) +{ + xce_job_preason_t a; + xce_job_preason_t b; + int round; + + for (round = 0; round < 4; round++) { + char* why = NULL; + + memset(&a, 0x0, sizeof(a)); + memset(&b, 0x0, sizeof(b)); + TEST_ASSERT_EQUAL_INT(1, add_job_preason(&a, 1, "h1", NULL)); + TEST_ASSERT_EQUAL_INT(1, add_job_preason(&a, 3, "h1", "mem")); + TEST_ASSERT_EQUAL_INT(1, add_job_preason(&b, 1, "h1", NULL)); + TEST_ASSERT_EQUAL_INT(1, add_job_preason(&b, 3, "h1", "mem")); + a.main_reason = 5; + b.main_reason = 5; + + switch (round) { + case 0: + b.main_reason = 6; + break; + case 1: + TEST_ASSERT_EQUAL_INT(2, add_job_preason(&b, 2, "h2", NULL)); + break; + case 2: + b.host_based_reasons[0]->reason = 9; + break; + default: + b.res_based_reasons[0]->reason = 9; + break; + } + + fail_vasprintf_at(0); + TEST_ASSERT_FALSE(job_preason_eq(&a, &b, &why)); + TEST_ASSERT_NULL(why); + TEST_ASSERT_TRUE(g_vasprintf_calls >= 1); /* the wrap was reached */ + + /* unarmed: the description is produced as usual */ + g_vasprintf_fail_at = -1; + TEST_ASSERT_FALSE(job_preason_eq(&a, &b, &why)); + TEST_ASSERT_NOT_NULL(why); + free(why); + + erase_job_preason(&a); + erase_job_preason(&b); + } + + /* top-level NULL */ + { + xce_job_preason_t c; + char* why = NULL; + + memset(&c, 0x0, sizeof(c)); + fail_vasprintf_at(0); + TEST_ASSERT_FALSE(job_preason_eq(&c, NULL, &why)); + TEST_ASSERT_NULL(why); + TEST_ASSERT_EQUAL_INT(1, g_vasprintf_calls); + + /* equal: no description, so nothing to fail */ + fail_vasprintf_at(0); + why = (char*)&g_garbage; + TEST_ASSERT_TRUE(job_preason_eq(&c, &c, &why)); + TEST_ASSERT_NULL(why); + TEST_ASSERT_EQUAL_INT(0, g_vasprintf_calls); + } +} + int main(void) { UNITY_BEGIN(); @@ -325,6 +399,7 @@ int main(void) RUN_TEST(test_merge_job_alloc_strdup_failure_reports_enomem); RUN_TEST(test_alloc_eq_description_oom_leaves_why_null); RUN_TEST(test_job_alloc_eq_description_oom_at_every_level); + RUN_TEST(test_job_preason_eq_description_oom_leaves_why_null); return UNITY_END(); }