feat(xce-harness-component): relocate the four standalone test binaries
Move test_resval_typeresolve.c, test_joblist_display.c, test_join_worker_args.c, and test_listrow_json.c into src/harness/testsuite/, and re-create their executable()/test() blocks verbatim in src/harness/meson.build under suite 'harness' (task 1.1 had already fully deleted them from src/controller/meson.build, not just moved them, so these had been orphaned on disk since that commit). Fix test_joblist_display.c's ctrlstatus_fmt.h include path to the xce/ location promoted in task 1.2. Verified against the pre-task-1.1 baseline (git show cf55d691^): all four blocks' source lists, c_args, and env are faithfully reproduced, only paths and the suite tag changed. harness suite: 5 binaries, all green (resval_typeresolve 4, joblist_display 20, join_worker_args 5, listrow_json 12, harness_unit_test 132 -- 173 total, 0 failures). controller suite unchanged: 1199 Tests 0 Failures 0 Ignored. This completes Major Task 2 (Core) -- the harness component now owns all 5 of its test binaries independently of the controller build. Task 2.2 of xce-harness-component. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
b5873a67aa
commit
49cf25aa23
6 changed files with 114 additions and 2 deletions
|
|
@ -1,256 +0,0 @@
|
|||
/* test_joblist_display.c -- xce-test-cli-ux task 2.5 remediation
|
||||
*
|
||||
* Unity unit tests for joblist_display.c's worker_status_str()/
|
||||
* job_state_str()/format_job_time(), extracted from test.c (see
|
||||
* joblist_display.h for the full extraction rationale). All three fixes
|
||||
* these functions carry (bug-1711) were already verified correct by review
|
||||
* against the pre-extraction code; this file is the permanent, automated
|
||||
* proof that (a) the fixes behave as intended and (b) the extraction did not
|
||||
* change any observed behavior -- in particular, that the pre-existing
|
||||
* branches these fixes sit alongside do NOT regress into the new fallback
|
||||
* branches.
|
||||
*
|
||||
* Standalone Unity main (this binary is its own test executable -- see
|
||||
* meson.build's joblist_display_unit_test), mirroring
|
||||
* resval_typeresolve_unit_test's/host_runusage_unit_test's own pattern.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "ctrlstatus_fmt.h" /* ctrlstatus_format_time_ms -- used directly
|
||||
* below to prove format_job_time() genuinely
|
||||
* delegates to it (byte-identical output),
|
||||
* rather than a parallel reimplementation that
|
||||
* could silently drift. */
|
||||
#include "joblist_display.h"
|
||||
#include "xce/types.h" /* XCE_WORKER_*, JOB_STAT_* bit definitions */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* worker_status_str() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Requirement 11.1's non-regression proof: admin-set CLOSED must still take
|
||||
* priority over the dynamic UNAVAIL bit when both are set (task 8.6's
|
||||
* config-upgrade-snapshot soak relies on this precedence). */
|
||||
static void test_worker_status_closed_over_unavail_priority(void)
|
||||
{
|
||||
char* rc = worker_status_str(XCE_WORKER_CLOSED | XCE_WORKER_UNAVAIL);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(
|
||||
"CLOSED", rc,
|
||||
"CLOSED must take priority over UNAVAIL when both bits are set");
|
||||
}
|
||||
|
||||
static void test_worker_status_unavail_alone(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("UNAVAIL",
|
||||
worker_status_str(XCE_WORKER_UNAVAIL));
|
||||
}
|
||||
|
||||
static void test_worker_status_lockedu_alone(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("LOCKU", worker_status_str(XCE_WORKER_LOCKEDU));
|
||||
}
|
||||
|
||||
static void test_worker_status_lockedw_alone(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("LOCKW", worker_status_str(XCE_WORKER_LOCKEDW));
|
||||
}
|
||||
|
||||
static void test_worker_status_busy_alone(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("BUSY", worker_status_str(XCE_WORKER_BUSY));
|
||||
}
|
||||
|
||||
static void test_worker_status_zero_is_ok(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("OK", worker_status_str(0));
|
||||
}
|
||||
|
||||
/* Requirement 11.1 (bug-1711's actual fix): an unrecognized status bit
|
||||
* (XCE_WORKER_CLOSED_BY_ADMIN -- a real bit in xce/types.h, but not one
|
||||
* worker_status_str() checks for) must fall through to "UNKNOWN", correctly
|
||||
* spelled -- previously misspelled "UNKOWN". */
|
||||
static void test_worker_status_unrecognized_bit_is_unknown(void)
|
||||
{
|
||||
char* rc = worker_status_str(XCE_WORKER_CLOSED_BY_ADMIN);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(
|
||||
"UNKNOWN", rc,
|
||||
"an unrecognized status bit must report correctly-spelled UNKNOWN "
|
||||
"(Requirement 11.1)");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* job_state_str() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void test_job_state_pend(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("PEND", job_state_str(JOB_STAT_PEND));
|
||||
}
|
||||
|
||||
static void test_job_state_pend_psusp(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"PSUSP", job_state_str(JOB_STAT_PEND | JOB_STAT_PSUSP));
|
||||
}
|
||||
|
||||
static void test_job_state_run(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("RUN", job_state_str(JOB_STAT_RUN));
|
||||
}
|
||||
|
||||
/* Non-regression proof (review's specific request): RUN+USUSP must still
|
||||
* resolve to "USUSP", not fall through to the new RUN+EXIT fallback. */
|
||||
static void test_job_state_run_ususp(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(
|
||||
"USUSP", job_state_str(JOB_STAT_RUN | JOB_STAT_USUSP),
|
||||
"RUN+USUSP must not regress into the new RUN+EXIT fallback");
|
||||
}
|
||||
|
||||
/* Non-regression proof (review's specific request): RUN+SSUSP must still
|
||||
* resolve to "SSUSP", not fall through to the new RUN+EXIT fallback. */
|
||||
static void test_job_state_run_ssusp(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(
|
||||
"SSUSP", job_state_str(JOB_STAT_RUN | JOB_STAT_SSUSP),
|
||||
"RUN+SSUSP must not regress into the new RUN+EXIT fallback");
|
||||
}
|
||||
|
||||
/* Requirement 11.7 (bug-1711's actual fix): RUN+EXIT (e.g. a worker-reported
|
||||
* FAIL received without a prior FINISH) must report the raw-state fallback
|
||||
* "RUN(0x%x)", mirroring print_jobinfo's own state_raw guard for the same
|
||||
* ambiguity, instead of silently printing plain "RUN". */
|
||||
static void test_job_state_run_exit_fallback(void)
|
||||
{
|
||||
int state = JOB_STAT_RUN | JOB_STAT_EXIT;
|
||||
char expected[32];
|
||||
char* rc = job_state_str(state);
|
||||
|
||||
snprintf(expected, sizeof(expected), "RUN(0x%x)", state);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(
|
||||
expected, rc,
|
||||
"RUN+EXIT must report the raw-state fallback RUN(0x%x) "
|
||||
"(Requirement 11.7)");
|
||||
}
|
||||
|
||||
static void test_job_state_exit(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("EXIT", job_state_str(JOB_STAT_EXIT));
|
||||
}
|
||||
|
||||
static void test_job_state_done(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("DONE", job_state_str(JOB_STAT_DONE));
|
||||
}
|
||||
|
||||
static void test_job_state_error(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("ERROR", job_state_str(JOB_STAT_ERR));
|
||||
}
|
||||
|
||||
/* Default/unknown branch: no recognized bit set at all. */
|
||||
static void test_job_state_unknown_default(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("UNKNOWN", job_state_str(0));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* format_job_time() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Requirement 11.5: a zero job time_t (event has not happened yet) must
|
||||
* print "-", not a misleading 1970-01-01 epoch date. */
|
||||
static void test_format_job_time_zero_is_dash(void)
|
||||
{
|
||||
char buf[32];
|
||||
const char* rc = format_job_time((time_t)0, buf, sizeof(buf));
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("-", rc);
|
||||
}
|
||||
|
||||
/* Requirement 11.5's delegation proof: format_job_time() is supposed to be a
|
||||
* thin seconds->milliseconds adapter directly in front of
|
||||
* ctrlstatus_format_time_ms() -- this asserts the two are BYTE-IDENTICAL for
|
||||
* the equivalent millisecond input, proving genuine delegation rather than a
|
||||
* parallel reimplementation that could silently drift from ctrlstatus's own
|
||||
* formatting. */
|
||||
static void test_format_job_time_matches_ctrlstatus_formatter(void)
|
||||
{
|
||||
time_t t = (time_t)1700000000; /* arbitrary fixed nonzero epoch */
|
||||
char via_format_job_time[32];
|
||||
char via_ctrlstatus_direct[32];
|
||||
const char* rc1;
|
||||
char* rc2;
|
||||
|
||||
rc1 = format_job_time(t, via_format_job_time, sizeof(via_format_job_time));
|
||||
rc2 = ctrlstatus_format_time_ms((uint64_t)t * 1000u,
|
||||
via_ctrlstatus_direct,
|
||||
sizeof(via_ctrlstatus_direct));
|
||||
|
||||
TEST_ASSERT_NOT_NULL(rc1);
|
||||
TEST_ASSERT_NOT_NULL(rc2);
|
||||
TEST_ASSERT_EQUAL_STRING_MESSAGE(
|
||||
rc2, rc1,
|
||||
"format_job_time() must be byte-identical to a direct "
|
||||
"ctrlstatus_format_time_ms() call on the equivalent millisecond "
|
||||
"input -- it must delegate, not reimplement");
|
||||
}
|
||||
|
||||
/* Bonus branch coverage: format_job_time()'s own "-" fallback when the
|
||||
* underlying formatter declines (buffer too small per
|
||||
* ctrlstatus_format_time_ms's documented >= 24-byte contract). */
|
||||
static void test_format_job_time_buffer_too_small_is_dash(void)
|
||||
{
|
||||
char buf[4]; /* below ctrlstatus_format_time_ms's 24-byte floor */
|
||||
const char* rc = format_job_time((time_t)1700000000, buf, sizeof(buf));
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("-", rc);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Standalone Unity main (this binary is its own test executable -- see */
|
||||
/* meson.build's joblist_display_unit_test) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("\n>> xce-test-cli-ux task 2.5 remediation: joblist_display "
|
||||
"worker_status_str/job_state_str/format_job_time\n\n");
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_worker_status_closed_over_unavail_priority);
|
||||
RUN_TEST(test_worker_status_unavail_alone);
|
||||
RUN_TEST(test_worker_status_lockedu_alone);
|
||||
RUN_TEST(test_worker_status_lockedw_alone);
|
||||
RUN_TEST(test_worker_status_busy_alone);
|
||||
RUN_TEST(test_worker_status_zero_is_ok);
|
||||
RUN_TEST(test_worker_status_unrecognized_bit_is_unknown);
|
||||
|
||||
RUN_TEST(test_job_state_pend);
|
||||
RUN_TEST(test_job_state_pend_psusp);
|
||||
RUN_TEST(test_job_state_run);
|
||||
RUN_TEST(test_job_state_run_ususp);
|
||||
RUN_TEST(test_job_state_run_ssusp);
|
||||
RUN_TEST(test_job_state_run_exit_fallback);
|
||||
RUN_TEST(test_job_state_exit);
|
||||
RUN_TEST(test_job_state_done);
|
||||
RUN_TEST(test_job_state_error);
|
||||
RUN_TEST(test_job_state_unknown_default);
|
||||
|
||||
RUN_TEST(test_format_job_time_zero_is_dash);
|
||||
RUN_TEST(test_format_job_time_matches_ctrlstatus_formatter);
|
||||
RUN_TEST(test_format_job_time_buffer_too_small_is_dash);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
/* test_join_worker_args.c -- xce-test-cli-ux task 2.6 remediation
|
||||
* (bug-1557 fix)
|
||||
*
|
||||
* Unity unit tests for join_worker_args.c's join_worker_parse_attrs(),
|
||||
* extracted from test.c's cmd_join_worker() (see join_worker_args.h for the
|
||||
* full extraction rationale). This is the permanent, automated regression
|
||||
* guard for bug-1557 (join's stale maxmem default): review had confirmed
|
||||
* the fix value/unit were already correct in cmd_join_worker, but rejected
|
||||
* that task for lacking a permanent test given a genuine testable seam
|
||||
* existed -- this file is that test.
|
||||
*
|
||||
* Standalone Unity main (this binary is its own test executable -- see
|
||||
* meson.build's join_worker_args_unit_test), mirroring
|
||||
* joblist_display_unit_test's/resval_typeresolve_unit_test's own pattern.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "join_worker_args.h"
|
||||
#include "xce/types.h" /* hostattrs_t */
|
||||
|
||||
/* Requirement 12.1's actual regression guard: no -m flag given must default
|
||||
* maxmem to 64 * 1024 * 1024 (64 MB expressed in the codebase's
|
||||
* bytes-canonical unit) -- not the pre-bug-1557 nonsensical 64 bytes. */
|
||||
static void test_join_worker_default_maxmem_is_64mb_in_bytes(void)
|
||||
{
|
||||
char* argv[] = { "join" };
|
||||
hostattrs_t attrs;
|
||||
int rc = join_worker_parse_attrs(1, argv, &attrs);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
/* This Unity build has double-precision assertions disabled -- use
|
||||
* TEST_ASSERT_EQUAL_FLOAT (this file's own convention, matching
|
||||
* test_listfmt_json.c/test_wal_job_rusage_fact.c) instead. 64 * 1024 *
|
||||
* 1024 is an exact power of two, so the float round-trip is exact. */
|
||||
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
||||
(float)(64 * 1024 * 1024), (float)attrs.maxmem,
|
||||
"join's default maxmem must be 64 * 1024 * 1024 bytes (bug-1557, "
|
||||
"Requirement 12.1)");
|
||||
}
|
||||
|
||||
/* Proves the flag-parsing itself still works after extraction, not just the
|
||||
* default: -m <value> must override the default exactly. */
|
||||
static void test_join_worker_dash_m_overrides_default(void)
|
||||
{
|
||||
char* argv[] = { "join", "-m", "12345" };
|
||||
hostattrs_t attrs;
|
||||
int rc = join_worker_parse_attrs(3, argv, &attrs);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
||||
12345.0f, (float)attrs.maxmem,
|
||||
"-m <value> must override join's default maxmem");
|
||||
}
|
||||
|
||||
/* Fail-loud behavior (matching this file's error-handling convention
|
||||
* elsewhere): an unrecognized flag must return the negative-errno error
|
||||
* code, not silently succeed with a partially-parsed attrs. */
|
||||
static void test_join_worker_unrecognized_flag_returns_einval(void)
|
||||
{
|
||||
char* argv[] = { "join", "-z", "bogus" };
|
||||
hostattrs_t attrs;
|
||||
int rc = join_worker_parse_attrs(3, argv, &attrs);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
/* A NULL attrs pointer must also fail loud rather than crash. */
|
||||
static void test_join_worker_null_attrs_returns_einval(void)
|
||||
{
|
||||
char* argv[] = { "join" };
|
||||
int rc = join_worker_parse_attrs(1, argv, NULL);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
/* Documents/locks in the CURRENT (still-buggy, unchanged-by-this-task)
|
||||
* default values of maxcpus/maxswap/maxtmp -- the sibling same-class fields
|
||||
* task 2.6's implementer correctly declined to touch (a separate,
|
||||
* recommended follow-up bug covers them). If someone later fixes those
|
||||
* fields, THIS test's assertions (specific to maxmem) must not be confused
|
||||
* with assertions on those separate fields. */
|
||||
static void test_join_worker_sibling_defaults_unchanged(void)
|
||||
{
|
||||
char* argv[] = { "join" };
|
||||
hostattrs_t attrs;
|
||||
int rc = join_worker_parse_attrs(1, argv, &attrs);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
4, attrs.maxcpus,
|
||||
"maxcpus default is unchanged by task 2.6 (bug-1557 scoped to "
|
||||
"maxmem only)");
|
||||
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
||||
1024.0f, (float)attrs.maxswap,
|
||||
"maxswap's still-buggy default is intentionally unchanged by task "
|
||||
"2.6 -- separate follow-up bug covers it");
|
||||
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
||||
2048.0f, (float)attrs.maxtmp,
|
||||
"maxtmp's still-buggy default is intentionally unchanged by task "
|
||||
"2.6 -- separate follow-up bug covers it");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Standalone Unity main (this binary is its own test executable -- see */
|
||||
/* meson.build's join_worker_args_unit_test) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("\n>> xce-test-cli-ux task 2.6 remediation: join_worker_args "
|
||||
"join_worker_parse_attrs (bug-1557)\n\n");
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_join_worker_default_maxmem_is_64mb_in_bytes);
|
||||
RUN_TEST(test_join_worker_dash_m_overrides_default);
|
||||
RUN_TEST(test_join_worker_unrecognized_flag_returns_einval);
|
||||
RUN_TEST(test_join_worker_null_attrs_returns_einval);
|
||||
RUN_TEST(test_join_worker_sibling_defaults_unchanged);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
/* test_listrow_json.c -- xce-test-cli-ux task 3.4 (Requirements 5.1, 5.2,
|
||||
* 5.3) unit tests for listrow_json.c's pure per-command JSON-row
|
||||
* field-mapping helpers.
|
||||
*
|
||||
* Standalone Unity binary with its own main() -- mirrors
|
||||
* test_joblist_display.c's/test_resval_typeresolve.c's precedent (task
|
||||
* 2.5/resource-value-verification task 1.1): listrow_json.c has zero
|
||||
* dependency on test.c or any networking, so it is built+tested in
|
||||
* isolation rather than folded into controller_test.c's shared suite.
|
||||
*
|
||||
* Every test builds a JSON row directly with the module under test, then
|
||||
* either inspects the in-memory cJSON tree (lifecycle-only assertions) or
|
||||
* routes it through listfmt_json_finish() (task 2.2, unmodified) to prove
|
||||
* a genuine JSON-text round trip -- this is where the fractional-value
|
||||
* (cpufactor) round-trip test lives, closing the gap task 2.2's own
|
||||
* Implementation Notes flagged ("listfmt_json.c's tests only exercise
|
||||
* power-of-two doubles ... task 3.4 should add a real fractional-value
|
||||
* round-trip test once actual command fields are wired in").
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "listfmt_json.h"
|
||||
#include "listrow_json.h"
|
||||
|
||||
/* ---- stdout capture, for the listfmt_json_finish()-based round-trip
|
||||
* tests -- mirrors test_listfmt_json.c's own dup/dup2/freopen technique
|
||||
* exactly (that file's tests establish the pattern this one reuses). */
|
||||
|
||||
static char g_dir[256];
|
||||
|
||||
static void fresh_dir(void)
|
||||
{
|
||||
strcpy(g_dir, "/tmp/xce_listrow_json_test_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(g_dir));
|
||||
}
|
||||
|
||||
static void cleanup_dir(void)
|
||||
{
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", g_dir);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int saved_stdout_fd;
|
||||
char stdout_path[300];
|
||||
} capture_t;
|
||||
|
||||
static void capture_begin(capture_t* cap)
|
||||
{
|
||||
fflush(stdout);
|
||||
cap->saved_stdout_fd = dup(fileno(stdout));
|
||||
TEST_ASSERT_TRUE(cap->saved_stdout_fd >= 0);
|
||||
snprintf(cap->stdout_path, sizeof(cap->stdout_path), "%s/out", g_dir);
|
||||
TEST_ASSERT_NOT_NULL(freopen(cap->stdout_path, "w", stdout));
|
||||
}
|
||||
|
||||
static void capture_end(capture_t* cap, char** out_stdout)
|
||||
{
|
||||
fflush(stdout);
|
||||
TEST_ASSERT_TRUE(dup2(cap->saved_stdout_fd, fileno(stdout)) >= 0);
|
||||
close(cap->saved_stdout_fd);
|
||||
clearerr(stdout);
|
||||
|
||||
FILE* f = fopen(cap->stdout_path, "r");
|
||||
TEST_ASSERT_NOT_NULL(f);
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
*out_stdout = malloc((size_t)len + 1);
|
||||
TEST_ASSERT_NOT_NULL(*out_stdout);
|
||||
size_t n = fread(*out_stdout, 1, (size_t)len, f);
|
||||
(*out_stdout)[n] = '\0';
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* ---- reslist_json_row ---------------------------------------------- */
|
||||
|
||||
static void test_reslist_json_row_maps_every_table_field(void)
|
||||
{
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
reslist_json_row(row, "mem", 0x2a, 60, "physical memory");
|
||||
|
||||
/* Every column cmd_reslist's table header prints (Name, Flags,
|
||||
* Interval, Description) must be present -- Requirement 5.1. */
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"mem", cJSON_GetStringValue(cJSON_GetObjectItem(row, "name")));
|
||||
TEST_ASSERT_EQUAL_STRING("physical memory", cJSON_GetStringValue(
|
||||
cJSON_GetObjectItem(
|
||||
row, "description")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
60.0f, (float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "interval")));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_reslist_json_row_flags_preserves_raw_numeric_value(void)
|
||||
{
|
||||
/* bug-1711 (Requirement 11.4) formats Flags as "0x%x" for the TABLE
|
||||
* only -- the JSON row must not lose the underlying value. 0x2a == 42
|
||||
* decimal; assert the JSON "flags" field carries that same integer,
|
||||
* not a hex string. */
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
reslist_json_row(row, "mem", 0x2a, 60, "physical memory");
|
||||
|
||||
cJSON* flags = cJSON_GetObjectItem(row, "flags");
|
||||
TEST_ASSERT_NOT_NULL(flags);
|
||||
TEST_ASSERT_TRUE(cJSON_IsNumber(flags));
|
||||
TEST_ASSERT_EQUAL_FLOAT(42.0f, (float)cJSON_GetNumberValue(flags));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_reslist_json_row_long_description_not_truncated(void)
|
||||
{
|
||||
/* Requirement 5.3: no truncation regardless of value length -- the
|
||||
* table's Description column is a fixed "%-64s" field; a value longer
|
||||
* than 64 characters exercises exactly the scenario the table format
|
||||
* would visually misalign but JSON must not lose or clip. */
|
||||
enum { LONG_LEN = 300 };
|
||||
char long_desc[LONG_LEN + 1];
|
||||
for (int i = 0; i < LONG_LEN; i++)
|
||||
long_desc[i] = (char)('a' + (i % 26));
|
||||
long_desc[LONG_LEN] = '\0';
|
||||
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
reslist_json_row(row, "longres", 0, 1, long_desc);
|
||||
|
||||
const char* desc =
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(row, "description"));
|
||||
TEST_ASSERT_NOT_NULL(desc);
|
||||
TEST_ASSERT_EQUAL_INT(LONG_LEN, (int)strlen(desc));
|
||||
TEST_ASSERT_EQUAL_STRING(long_desc, desc);
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_reslist_json_row_noop_on_null_row(void)
|
||||
{
|
||||
/* Must not crash. */
|
||||
reslist_json_row(NULL, "mem", 1, 1, "desc");
|
||||
}
|
||||
|
||||
/* ---- workerlist_json_row --------------------------------------------- */
|
||||
|
||||
static void test_workerlist_json_row_maps_every_table_field(void)
|
||||
{
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
workerlist_json_row(row, "worker-07", "OK", "linux", "x86_64", 1.0, 8,
|
||||
67108864.0, 134217728.0, 268435456.0, 2);
|
||||
|
||||
/* Every column cmd_workerlist's table header prints (HostName, Status,
|
||||
* HostType, HostModel, CPUfactor, MaxCPU, MaxMem, MaxSwap, MaxTmp,
|
||||
* NDisks) must be present -- Requirement 5.1. */
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"worker-07",
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(row, "hostname")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"OK", cJSON_GetStringValue(cJSON_GetObjectItem(row, "status")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"linux", cJSON_GetStringValue(cJSON_GetObjectItem(row, "hosttype")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"x86_64",
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(row, "hostmodel")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
8.0f, (float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "maxcpu")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
2.0f, (float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "ndisks")));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_workerlist_json_row_maxmem_is_raw_bytes_not_humanized(void)
|
||||
{
|
||||
/* The table humanizes MaxMem/MaxSwap/MaxTmp via format_bytes_human()
|
||||
* (e.g. "64.0 MB") for DISPLAY only; the JSON row must carry the raw
|
||||
* byte value instead, so a script can consume it without re-parsing a
|
||||
* unit suffix. Use a value that is NOT a round unit boundary to prove
|
||||
* this is the raw byte count, not a rounded "MB" figure. */
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
workerlist_json_row(row, "worker-01", "OK", "linux", "x86_64", 1.0, 4,
|
||||
100000000.0, 0.0, 0.0, 1);
|
||||
|
||||
cJSON* maxmem = cJSON_GetObjectItem(row, "maxmem");
|
||||
TEST_ASSERT_NOT_NULL(maxmem);
|
||||
TEST_ASSERT_TRUE(cJSON_IsNumber(maxmem));
|
||||
TEST_ASSERT_EQUAL_FLOAT(100000000.0f, (float)cJSON_GetNumberValue(maxmem));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
/* This is the fractional-value round-trip test task 2.2's Implementation
|
||||
* Note explicitly asked for: listfmt_json.c's own tests only exercised
|
||||
* power-of-two doubles; cpufactor is a genuine command field that is
|
||||
* commonly fractional (e.g. a machine rated at 1.5x or 0.75x a reference
|
||||
* host), and this proves the value survives an ACTUAL JSON-text
|
||||
* round-trip (via listfmt_json_finish()'s real cJSON_Print() path, then
|
||||
* re-parsed with cJSON_Parse()) bit-for-bit, not just while sitting in the
|
||||
* in-memory cJSON tree. */
|
||||
static void test_workerlist_json_row_cpufactor_fractional_value_roundtrips(void)
|
||||
{
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
workerlist_json_row(row, "worker-09", "OK", "linux", "x86_64", 12.34, 4,
|
||||
4294967296.0, 0.0, 0.0, 1);
|
||||
|
||||
fresh_dir();
|
||||
capture_t cap;
|
||||
capture_begin(&cap);
|
||||
listfmt_json_finish(array); /* prints then frees `array`/`row` */
|
||||
char* out;
|
||||
capture_end(&cap, &out);
|
||||
|
||||
cJSON* parsed = cJSON_Parse(out);
|
||||
TEST_ASSERT_NOT_NULL(parsed);
|
||||
TEST_ASSERT_EQUAL_INT(1, cJSON_GetArraySize(parsed));
|
||||
|
||||
cJSON* p0 = cJSON_GetArrayItem(parsed, 0);
|
||||
cJSON* cpufactor = cJSON_GetObjectItem(p0, "cpufactor");
|
||||
TEST_ASSERT_NOT_NULL(cpufactor);
|
||||
TEST_ASSERT_TRUE(cJSON_IsNumber(cpufactor));
|
||||
/* Unity is built without UNITY_INCLUDE_DOUBLE (this codebase's
|
||||
* convention) -- both sides are cast to float for comparison, same as
|
||||
* test_listfmt_json.c's own pattern; since 12.34 round-trips exactly
|
||||
* at double precision (cJSON's shortest-round-trippable-double
|
||||
* formatting), the float cast of both sides is still an exact
|
||||
* equality check, not merely "close enough". */
|
||||
TEST_ASSERT_EQUAL_FLOAT(12.34f, (float)cJSON_GetNumberValue(cpufactor));
|
||||
|
||||
/* A non-power-of-two maxmem value in the SAME row must also survive,
|
||||
* proving the fractional field didn't corrupt a neighboring one. */
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
4294967296.0f,
|
||||
(float)cJSON_GetNumberValue(cJSON_GetObjectItem(p0, "maxmem")));
|
||||
|
||||
cJSON_Delete(parsed);
|
||||
free(out);
|
||||
cleanup_dir();
|
||||
}
|
||||
|
||||
static void test_workerlist_json_row_noop_on_null_row(void)
|
||||
{
|
||||
workerlist_json_row(NULL, "h", "OK", "t", "m", 1.0, 1, 1.0, 1.0, 1.0, 1);
|
||||
}
|
||||
|
||||
/* ---- joblist_json_row -------------------------------------------------- */
|
||||
|
||||
static void test_joblist_json_row_maps_every_table_field(void)
|
||||
{
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
joblist_json_row(row, "1.0", "alice", "normal", "RUN",
|
||||
(time_t)1000, (time_t)1010, (time_t)0, 2, 4,
|
||||
"/bin/sleep 100");
|
||||
|
||||
/* Every column joblist_query's table header prints (JobId, User,
|
||||
* Queue, State, SubmitTime, StartTime, FinishTime, MinTasks, MaxTasks,
|
||||
* Command) must be present -- Requirement 5.1, including the
|
||||
* mintasks/maxtasks pair as two distinct fields (mirroring
|
||||
* Requirement 11.6's table-side fix, not a single ambiguous
|
||||
* "ntasks"). */
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"1.0", cJSON_GetStringValue(cJSON_GetObjectItem(row, "jobid")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"alice", cJSON_GetStringValue(cJSON_GetObjectItem(row, "user")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"normal", cJSON_GetStringValue(cJSON_GetObjectItem(row, "queue")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"RUN", cJSON_GetStringValue(cJSON_GetObjectItem(row, "state")));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"/bin/sleep 100",
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(row, "command")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
1000.0f,
|
||||
(float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "submit_time")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
1010.0f,
|
||||
(float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "start_time")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
0.0f,
|
||||
(float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "finish_time")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
2.0f,
|
||||
(float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "mintasks")));
|
||||
TEST_ASSERT_EQUAL_FLOAT(
|
||||
4.0f,
|
||||
(float)cJSON_GetNumberValue(cJSON_GetObjectItem(row, "maxtasks")));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_joblist_json_row_ambiguous_state_fallback_kept_verbatim(void)
|
||||
{
|
||||
/* Requirement 11.7's raw-state fallback ("RUN(0x38)"-style text from
|
||||
* job_state_str()) must not be re-interpreted or stripped -- the JSON
|
||||
* row stores whatever string the caller passed in. */
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
joblist_json_row(row, "2.0", "bob", "normal", "RUN(0x38)", (time_t)5,
|
||||
(time_t)6, (time_t)7, 1, 1, "cmd");
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"RUN(0x38)",
|
||||
cJSON_GetStringValue(cJSON_GetObjectItem(row, "state")));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_joblist_json_row_unstarted_job_times_are_zero_not_dash(void)
|
||||
{
|
||||
/* format_job_time() renders a 0 time_t as "-" for the human table
|
||||
* (Requirement 11.5); the JSON row instead keeps the raw 0 as a
|
||||
* NUMBER (not the string "-") so a script can compare/sort on it
|
||||
* without special-casing a sentinel string. */
|
||||
cJSON* array = listfmt_json_begin();
|
||||
cJSON* row = listfmt_json_new_row(array);
|
||||
|
||||
joblist_json_row(row, "3.0", "carol", "normal", "PEND", (time_t)0,
|
||||
(time_t)0, (time_t)0, 1, 1, "cmd");
|
||||
|
||||
cJSON* start = cJSON_GetObjectItem(row, "start_time");
|
||||
TEST_ASSERT_NOT_NULL(start);
|
||||
TEST_ASSERT_TRUE(cJSON_IsNumber(start));
|
||||
TEST_ASSERT_EQUAL_FLOAT(0.0f, (float)cJSON_GetNumberValue(start));
|
||||
|
||||
cJSON_Delete(array);
|
||||
}
|
||||
|
||||
static void test_joblist_json_row_noop_on_null_row(void)
|
||||
{
|
||||
joblist_json_row(NULL, "1.0", "u", "q", "RUN", 1, 1, 1, 1, 1, "c");
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("\n>> xce-test-cli-ux task 3.4: listrow_json JSON-row "
|
||||
"field-mapping helpers (reslist/workerlist/joblist)\n\n");
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_reslist_json_row_maps_every_table_field);
|
||||
RUN_TEST(test_reslist_json_row_flags_preserves_raw_numeric_value);
|
||||
RUN_TEST(test_reslist_json_row_long_description_not_truncated);
|
||||
RUN_TEST(test_reslist_json_row_noop_on_null_row);
|
||||
|
||||
RUN_TEST(test_workerlist_json_row_maps_every_table_field);
|
||||
RUN_TEST(test_workerlist_json_row_maxmem_is_raw_bytes_not_humanized);
|
||||
RUN_TEST(test_workerlist_json_row_cpufactor_fractional_value_roundtrips);
|
||||
RUN_TEST(test_workerlist_json_row_noop_on_null_row);
|
||||
|
||||
RUN_TEST(test_joblist_json_row_maps_every_table_field);
|
||||
RUN_TEST(test_joblist_json_row_ambiguous_state_fallback_kept_verbatim);
|
||||
RUN_TEST(test_joblist_json_row_unstarted_job_times_are_zero_not_dash);
|
||||
RUN_TEST(test_joblist_json_row_noop_on_null_row);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/* test_resval_typeresolve.c -- resource-value-verification task 1.1
|
||||
*
|
||||
* Unity unit tests for resval_typeresolve.c's resource-name -> Value_u_which
|
||||
* resolution, extracted from test.c's set_resval() (design.md "Testing
|
||||
* Strategy"). Four cases, matching tasks.md task 1.1 exactly:
|
||||
*
|
||||
* 1. A currently-known numeric name (mem) resolves Value_u_fvalue, exactly
|
||||
* as today (Requirement 1.4) -- GREEN.
|
||||
* 2. A currently-known string name (type) resolves Value_u_svalue, exactly
|
||||
* as today (Requirement 1.4) -- GREEN.
|
||||
* 3. An unrecognized name (r15m) defaults to numeric (Requirement 1.1) --
|
||||
* RED: task 2.1 has not yet implemented the fallback.
|
||||
* 4. An unrecognized name with an explicit type-override resolves to the
|
||||
* requested type (Requirement 1.1) -- RED: task 2.1 has not yet wired
|
||||
* `type_override` in.
|
||||
*
|
||||
* Cases 3 and 4 are INTENTIONALLY FAILING (RED) evidence for this task --
|
||||
* see resval_typeresolve.h's "TASK 1.1 STATUS" note and tasks.md task 2.1,
|
||||
* which implements the behavior these two cases assert.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "resval_typeresolve.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tests */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* 1. Currently-known numeric name -- unchanged behavior (GREEN). */
|
||||
static void test_known_numeric_name_resolves_fvalue(void)
|
||||
{
|
||||
int rc = resval_resolve_type("mem", RESVAL_TYPE_UNKNOWN);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
Value_u_fvalue, rc,
|
||||
"a currently-known numeric resource name (mem) must still resolve "
|
||||
"Value_u_fvalue exactly as set_resval() does today");
|
||||
printf("test_known_numeric_name_resolves_fvalue: PASS\n");
|
||||
}
|
||||
|
||||
/* 2. Currently-known string name -- unchanged behavior (GREEN). */
|
||||
static void test_known_string_name_resolves_svalue(void)
|
||||
{
|
||||
int rc = resval_resolve_type("type", RESVAL_TYPE_UNKNOWN);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
Value_u_svalue, rc,
|
||||
"a currently-known string resource name (type) must still resolve "
|
||||
"Value_u_svalue exactly as set_resval() does today");
|
||||
printf("test_known_string_name_resolves_svalue: PASS\n");
|
||||
}
|
||||
|
||||
/* 3. Unrecognized name defaults to numeric -- RED (task 2.1 implements). */
|
||||
static void test_unrecognized_name_defaults_to_numeric(void)
|
||||
{
|
||||
int rc = resval_resolve_type("r15m", RESVAL_TYPE_UNKNOWN);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
Value_u_fvalue, rc,
|
||||
"an unrecognized resource name (r15m) with no type override must "
|
||||
"default to Value_u_fvalue (Requirement 1.1) -- NOT YET "
|
||||
"IMPLEMENTED, expected RED until task 2.1");
|
||||
printf("test_unrecognized_name_defaults_to_numeric: PASS\n");
|
||||
}
|
||||
|
||||
/* 4. Unrecognized name + explicit override -- RED (task 2.1 implements). */
|
||||
static void test_unrecognized_name_with_override_resolves_requested_type(
|
||||
void)
|
||||
{
|
||||
int rc = resval_resolve_type("r15m", Value_u_svalue);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
Value_u_svalue, rc,
|
||||
"an unrecognized resource name (r15m) with an explicit type "
|
||||
"override must resolve to the requested type (Requirement 1.1) -- "
|
||||
"NOT YET IMPLEMENTED, expected RED until task 2.1");
|
||||
printf(
|
||||
"test_unrecognized_name_with_override_resolves_requested_type: "
|
||||
"PASS\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Standalone Unity main (this binary is its own test executable -- see */
|
||||
/* meson.build's resval_typeresolve_unit_test) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("\n>> resource-value-verification task 1.1: resval_typeresolve "
|
||||
"type resolution\n\n");
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_known_numeric_name_resolves_fvalue);
|
||||
RUN_TEST(test_known_string_name_resolves_svalue);
|
||||
RUN_TEST(test_unrecognized_name_defaults_to_numeric);
|
||||
RUN_TEST(test_unrecognized_name_with_override_resolves_requested_type);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue