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) <noreply@anthropic.com>
This commit is contained in:
parent
a6cbb323b0
commit
62d0b8b686
5 changed files with 648 additions and 2 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
/* vasprintf() (GNU extension) builds job_preason_eq()'s difference
|
||||
* description. */
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <errno.h>
|
||||
#include <stdarg.h>
|
||||
|
|
@ -23,6 +25,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue