feat(worker-rejoin-resync): task 4.2 - worker answers job-state query with extras
Classify each queried job ID as running/finished-ok/finished-fail/ unrecognized (Req 3.1-3.3, 3.6), report unqueried running/recently- finished jobs as extras, and send both via task 1.4's paginated wire helper (Req 8.2, 8.3). Composes correctly with the already-implemented controller-side consumer (resync_apply_report). Adds xcecd_finished_job_retention_snapshot() to xcecd_worker_utils, a small additive companion needed to enumerate all retained jobs for the extras set (task 4.1 only exposed single-jobid lookup). Folds the still-open OPCode_jobStateQuery receive-dispatch wiring gap into task 4.3's scope in tasks.md, since 4.3 already needs the real receive path to implement its post-join gate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y4M5MJM2zK6nU43da267Ku
This commit is contained in:
parent
e31012e8a7
commit
cfe58c3dcf
7 changed files with 1084 additions and 4 deletions
|
|
@ -107,3 +107,19 @@ test('worker finished_job_retention test', finished_job_retention_test,
|
|||
suite: 'worker',
|
||||
timeout: 60,
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
||||
# worker-rejoin-resync task 4.2 (Requirements 3.1, 3.2, 3.3, 3.6, 8.2, 8.3):
|
||||
# answer the controller's job-state query (running list + task 4.1's
|
||||
# finished-job retention), including extras -- white-box #include of both
|
||||
# ../src/xcecd_worker_utils.c and ../src/xcecd_resync.c, same convention
|
||||
# as finished_job_retention_test above.
|
||||
xcecd_resync_test = executable('xcecd_resync_test',
|
||||
'test_xcecd_resync.c',
|
||||
include_directories: [worker_test_incdir],
|
||||
dependencies: worker_test_deps,
|
||||
install: false)
|
||||
|
||||
test('worker xcecd_resync test', xcecd_resync_test,
|
||||
suite: 'worker',
|
||||
timeout: 60,
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
|
|
|||
686
src/worker/testsuite/test_xcecd_resync.c
Normal file
686
src/worker/testsuite/test_xcecd_resync.c
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
/*
|
||||
* test_xcecd_resync.c -- worker-rejoin-resync task 4.2 (Requirements 3.1,
|
||||
* 3.2, 3.3, 3.6, 8.2, 8.3): answer the controller's job-state query from
|
||||
* the worker's own live running list and the new finished-job retention
|
||||
* (task 4.1), including extra jobs the worker knows about that were not
|
||||
* part of the query, sent as one or more paginated OPCode_jobStateReport
|
||||
* messages.
|
||||
*
|
||||
* White-box #include of both ../src/xcecd_worker_utils.c (task 4.1's
|
||||
* retention + running-list lookups that xcecd_resync.c's classify logic
|
||||
* calls into for real, not against reimplemented stand-ins) and
|
||||
* ../src/xcecd_resync.c (this task's own production code under test) --
|
||||
* same convention as test_finished_job_retention.c and
|
||||
* test_controller_connector.c. Link-satisfying stubs for
|
||||
* xcecd_worker_utils.c's OTHER external dependencies are copied verbatim
|
||||
* from test_finished_job_retention.c's own header comment/stub set; none
|
||||
* of them are exercised by the assertions below.
|
||||
*
|
||||
* Wire-level assertions (paginated OPCode_jobStateReport sends) use
|
||||
* test_job_state_wire.c's own no-live-socket technique: a real (heap)
|
||||
* endpoint_t via endpoint_get() with ep->iomgr == NULL, so
|
||||
* xce_job_state_report_req() can be exercised end-to-end -- real
|
||||
* xce_encode() on send, real xce_decode() on "receive" -- without a
|
||||
* socket. Each queued page is popped with endpoint_deq()/ITPTR_TO_PACKET()
|
||||
* and decoded with xce_decode(), which takes ownership of (frees) the
|
||||
* packet.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "../src/xcecd_resync.c"
|
||||
#include "../src/xcecd_worker_utils.c"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Link-satisfying stubs -- none exercised by the assertions below; they
|
||||
* only need to let xcecd_worker_utils.c's other, unrelated code paths
|
||||
* link and (where reached at all) run to completion without touching
|
||||
* real sqlite3/dbus/IPC. Copied from test_finished_job_retention.c.
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
bool xcecd_reply_startjob(xcecd_job_t* job)
|
||||
{
|
||||
(void)job;
|
||||
return true;
|
||||
}
|
||||
|
||||
int xcecd_job_stopped(uint64_t job_id)
|
||||
{
|
||||
(void)job_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xcecd_job_resumed(uint64_t job_id)
|
||||
{
|
||||
(void)job_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xcecd_job_finished(xcecd_job_t* job)
|
||||
{
|
||||
(void)job;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool xcecd_db_update_worker(sqlite3* db, int worker_pid, int worker_status,
|
||||
int job_status)
|
||||
{
|
||||
(void)db;
|
||||
(void)worker_pid;
|
||||
(void)worker_status;
|
||||
(void)job_status;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool xcecd_db_delete_worker(sqlite3* db, int worker_pid)
|
||||
{
|
||||
(void)db;
|
||||
(void)worker_pid;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dbus_worker_clean(xcecd_worker_t* worker, xcecd_job_t* job)
|
||||
{
|
||||
(void)worker;
|
||||
(void)job;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ipc_master_check_connection() { return true; }
|
||||
|
||||
void block_signal(int sig) { (void)sig; }
|
||||
void unblock_signal(int sig) { (void)sig; }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Fixtures
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static xcecd_worker_t* make_worker(int pid, uint64_t jobid, int worker_status)
|
||||
{
|
||||
xcecd_worker_t* w = (xcecd_worker_t*)calloc(1, sizeof(*w));
|
||||
w->worker_pid = pid;
|
||||
w->worker_status = worker_status;
|
||||
w->job_status
|
||||
= (worker_status == WORKER_RUNNING) ? JOB_RUNNING : JOB_EXITED;
|
||||
w->job_info.jobid = jobid;
|
||||
return w;
|
||||
}
|
||||
|
||||
static void free_worker_list(xcecd_worker_t* list)
|
||||
{
|
||||
xcecd_worker_t* w = list;
|
||||
while (w) {
|
||||
xcecd_worker_t* next = w->next;
|
||||
free(w);
|
||||
w = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void link_worker(xcecd_worker_t** list, xcecd_worker_t* w)
|
||||
{
|
||||
if (*list == NULL) {
|
||||
*list = w;
|
||||
return;
|
||||
}
|
||||
xcecd_worker_t* cur = *list;
|
||||
while (cur->next) {
|
||||
cur = cur->next;
|
||||
}
|
||||
cur->next = w;
|
||||
w->prev = cur;
|
||||
}
|
||||
|
||||
static endpoint_t* new_test_endpoint(void)
|
||||
{
|
||||
endpoint_t* ep = endpoint_get();
|
||||
TEST_ASSERT_NOT_NULL(ep);
|
||||
return ep;
|
||||
}
|
||||
|
||||
static void pop_and_decode(endpoint_t* ep, xce_request_t* out)
|
||||
{
|
||||
data_item_t* it = endpoint_deq(ep);
|
||||
TEST_ASSERT_NOT_NULL(it);
|
||||
|
||||
io_packet_t* pkt = ITPTR_TO_PACKET(it);
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_decode(pkt, out));
|
||||
}
|
||||
|
||||
static void assert_queue_empty(endpoint_t* ep)
|
||||
{
|
||||
TEST_ASSERT_NULL(endpoint_deq(ep));
|
||||
}
|
||||
|
||||
void setUp(void) { xcecd_finished_job_retention_reset(); }
|
||||
|
||||
void tearDown(void) { xcecd_finished_job_retention_reset(); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* xcecd_resync_classify_job: per-ID classification (Requirements 3.1, 3.3)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static void test_classify_running_job(void)
|
||||
{
|
||||
xcecd_worker_t* w = make_worker(1, 111, WORKER_RUNNING);
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(w, 111, 5000, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(111, out.jobid);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_running, out.status);
|
||||
|
||||
free(w);
|
||||
}
|
||||
|
||||
static void test_classify_finished_ok_job(void)
|
||||
{
|
||||
xcecd_retain_finished_job(222, WORKER_DONE, 0, 90000);
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(NULL, 222, 95000, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(222, out.jobid);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_finishedOk, out.status);
|
||||
TEST_ASSERT_EQUAL_UINT64(90000, out.finish_time_ms);
|
||||
}
|
||||
|
||||
static void test_classify_finished_fail_job(void)
|
||||
{
|
||||
xcecd_retain_finished_job(333, WORKER_EXITED, 137, 90500);
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(NULL, 333, 95000, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(333, out.jobid);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_finishedFail, out.status);
|
||||
TEST_ASSERT_EQUAL_INT32(137, out.exit_code);
|
||||
TEST_ASSERT_EQUAL_UINT64(90500, out.finish_time_ms);
|
||||
}
|
||||
|
||||
static void test_classify_unrecognized_job(void)
|
||||
{
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(NULL, 444, 95000, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(444, out.jobid);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_unrecognized, out.status);
|
||||
}
|
||||
|
||||
/* A job present in the worker list but already terminal (worker_status
|
||||
* WORKER_DONE/EXITED, lingering before cleanup_worker_list() reaps it)
|
||||
* must NOT be reported as running -- it must fall through to retention
|
||||
* (populated by the same update_worker_status() hook at the same
|
||||
* transition, task 4.1), matching design.md's "check the running list
|
||||
* first; if not running, check retention" order exactly. */
|
||||
static void test_classify_terminal_worker_entry_falls_through_to_retention(void)
|
||||
{
|
||||
xcecd_worker_t* w = make_worker(5, 555, WORKER_DONE);
|
||||
xcecd_retain_finished_job(555, WORKER_DONE, 0, 80000);
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(w, 555, 85000, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_finishedOk, out.status);
|
||||
|
||||
free(w);
|
||||
}
|
||||
|
||||
/* Requirement 3.3: a job whose process state is inconclusive (here,
|
||||
* WORKER_UNKNOWN -- the one worker_status update_worker_status() never
|
||||
* feeds into retention) must be reported unrecognized, not guessed. */
|
||||
static void test_classify_worker_unknown_status_is_unrecognized(void)
|
||||
{
|
||||
xcecd_worker_t* w = make_worker(6, 666, WORKER_UNKNOWN);
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(w, 666, 85000, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_unrecognized, out.status);
|
||||
|
||||
free(w);
|
||||
}
|
||||
|
||||
/* Expiry boundary: a retained job older than the configured window must
|
||||
* classify as unrecognized, not finished (task 4.1's own boundary
|
||||
* semantics, consumed here through the classify layer). */
|
||||
static void test_classify_expired_retention_is_unrecognized(void)
|
||||
{
|
||||
xcecd_finished_job_retention_set_window_ms(1000);
|
||||
xcecd_retain_finished_job(777, WORKER_DONE, 0, 100000);
|
||||
xce_job_state_entry_t out;
|
||||
|
||||
xcecd_resync_classify_job(NULL, 777, 100000 + 1000 + 1, &out);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_unrecognized, out.status);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* xcecd_resync_build_report: query answers + extras (Requirements 3.2, 3.6)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static xce_job_state_entry_t* find_entry(xce_job_state_entry_t** entries, int n,
|
||||
uint64_t jobid)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (entries[i]->jobid == jobid) {
|
||||
return entries[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Every queried ID gets exactly one entry, none silently skipped, mixing
|
||||
* all four outcome kinds (Requirement 3.2, and the task's own Observable
|
||||
* Completion: "a query covering a mix of running, finished, and unknown
|
||||
* job IDs produces one correctly-classified entry per ID"). */
|
||||
static void test_build_report_answers_every_queried_id_exactly_once(void)
|
||||
{
|
||||
xcecd_worker_t* list = NULL;
|
||||
link_worker(&list, make_worker(1, 1001, WORKER_RUNNING));
|
||||
|
||||
xcecd_retain_finished_job(1002, WORKER_DONE, 0, 50000);
|
||||
xcecd_retain_finished_job(1003, WORKER_EXITED, 9, 50500);
|
||||
/* 1004 is queried but the worker has no record of it at all. */
|
||||
|
||||
uint64_t jobids[4] = { 1001, 1002, 1003, 1004 };
|
||||
xce_job_state_entry_t** entries = NULL;
|
||||
int n = 0;
|
||||
|
||||
int rc = xcecd_resync_build_report(list, jobids, 4, 60000, &entries, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(4, n);
|
||||
|
||||
xce_job_state_entry_t* e1001 = find_entry(entries, n, 1001);
|
||||
xce_job_state_entry_t* e1002 = find_entry(entries, n, 1002);
|
||||
xce_job_state_entry_t* e1003 = find_entry(entries, n, 1003);
|
||||
xce_job_state_entry_t* e1004 = find_entry(entries, n, 1004);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(e1001);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_running, e1001->status);
|
||||
TEST_ASSERT_EQUAL_INT(1, e1001->queried);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(e1002);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_finishedOk, e1002->status);
|
||||
TEST_ASSERT_EQUAL_UINT64(50000, e1002->finish_time_ms);
|
||||
TEST_ASSERT_EQUAL_INT(1, e1002->queried);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(e1003);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_finishedFail, e1003->status);
|
||||
TEST_ASSERT_EQUAL_INT32(9, e1003->exit_code);
|
||||
TEST_ASSERT_EQUAL_INT(1, e1003->queried);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(e1004);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_unrecognized, e1004->status);
|
||||
TEST_ASSERT_EQUAL_INT(1, e1004->queried);
|
||||
|
||||
xcecd_resync_free_entries(entries, n);
|
||||
free_worker_list(list);
|
||||
}
|
||||
|
||||
/* Requirement 3.6: a job the worker is running that was NOT part of the
|
||||
* query is reported as an extra (queried=0). */
|
||||
static void test_build_report_includes_running_extra(void)
|
||||
{
|
||||
xcecd_worker_t* list = NULL;
|
||||
link_worker(&list, make_worker(1, 2001, WORKER_RUNNING));
|
||||
link_worker(&list, make_worker(2, 2002, WORKER_RUNNING)); /* extra */
|
||||
|
||||
uint64_t jobids[1] = { 2001 };
|
||||
xce_job_state_entry_t** entries = NULL;
|
||||
int n = 0;
|
||||
|
||||
int rc = xcecd_resync_build_report(list, jobids, 1, 1000, &entries, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(2, n);
|
||||
|
||||
xce_job_state_entry_t* queried = find_entry(entries, n, 2001);
|
||||
xce_job_state_entry_t* extra = find_entry(entries, n, 2002);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(queried);
|
||||
TEST_ASSERT_EQUAL_INT(1, queried->queried);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(extra);
|
||||
TEST_ASSERT_EQUAL_INT(0, extra->queried);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_running, extra->status);
|
||||
|
||||
xcecd_resync_free_entries(entries, n);
|
||||
free_worker_list(list);
|
||||
}
|
||||
|
||||
/* Requirement 3.6: a job the worker recently finished that was NOT part
|
||||
* of the query is reported as an extra, with its real finish time. */
|
||||
static void test_build_report_includes_finished_extra(void)
|
||||
{
|
||||
xcecd_retain_finished_job(3001, WORKER_EXITED, 5, 70000);
|
||||
|
||||
xce_job_state_entry_t** entries = NULL;
|
||||
int n = 0;
|
||||
|
||||
int rc = xcecd_resync_build_report(NULL, NULL, 0, 75000, &entries, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, n);
|
||||
TEST_ASSERT_EQUAL_UINT64(3001, entries[0]->jobid);
|
||||
TEST_ASSERT_EQUAL_INT(JobStateStatus_finishedFail, entries[0]->status);
|
||||
TEST_ASSERT_EQUAL_INT32(5, entries[0]->exit_code);
|
||||
TEST_ASSERT_EQUAL_UINT64(70000, entries[0]->finish_time_ms);
|
||||
TEST_ASSERT_EQUAL_INT(0, entries[0]->queried);
|
||||
|
||||
xcecd_resync_free_entries(entries, n);
|
||||
}
|
||||
|
||||
/* A job that IS part of the query must never also appear a second time
|
||||
* as an extra -- the queried answer is the only entry for it. */
|
||||
static void test_build_report_queried_job_not_duplicated_as_extra(void)
|
||||
{
|
||||
xcecd_worker_t* list = NULL;
|
||||
link_worker(&list, make_worker(1, 4001, WORKER_RUNNING));
|
||||
xcecd_retain_finished_job(4002, WORKER_DONE, 0, 10000);
|
||||
|
||||
uint64_t jobids[2] = { 4001, 4002 };
|
||||
xce_job_state_entry_t** entries = NULL;
|
||||
int n = 0;
|
||||
|
||||
int rc = xcecd_resync_build_report(list, jobids, 2, 20000, &entries, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(2, n);
|
||||
|
||||
int count4001 = 0, count4002 = 0;
|
||||
int i;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (entries[i]->jobid == 4001)
|
||||
count4001++;
|
||||
if (entries[i]->jobid == 4002)
|
||||
count4002++;
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(1, count4001);
|
||||
TEST_ASSERT_EQUAL_INT(1, count4002);
|
||||
|
||||
xcecd_resync_free_entries(entries, n);
|
||||
free_worker_list(list);
|
||||
}
|
||||
|
||||
/* Empty query, nothing running or retained -> a valid, empty (zero-entry)
|
||||
* result, not an error. */
|
||||
static void test_build_report_empty_query_no_state_yields_empty_result(void)
|
||||
{
|
||||
xce_job_state_entry_t** entries
|
||||
= (xce_job_state_entry_t**)0x1; /* sentinel */
|
||||
int n = -1;
|
||||
|
||||
int rc = xcecd_resync_build_report(NULL, NULL, 0, 1000, &entries, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, n);
|
||||
TEST_ASSERT_NULL(entries);
|
||||
}
|
||||
|
||||
static void test_build_report_null_out_params_rejected(void)
|
||||
{
|
||||
uint64_t jobids[1] = { 1 };
|
||||
xce_job_state_entry_t** entries = NULL;
|
||||
int n = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
-EINVAL, xcecd_resync_build_report(NULL, jobids, 1, 1000, NULL, &n));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, xcecd_resync_build_report(
|
||||
NULL, jobids, 1, 1000, &entries, NULL));
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* xcecd_resync_answer_query: end-to-end paginated send (Requirements 8.2,
|
||||
* 8.3)
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static void test_answer_query_single_page(void)
|
||||
{
|
||||
xcecd_worker_t* list = NULL;
|
||||
link_worker(&list, make_worker(1, 5001, WORKER_RUNNING));
|
||||
xcecd_retain_finished_job(5002, WORKER_DONE, 0, 30000);
|
||||
|
||||
uint64_t jobids[2] = { 5001, 5002 };
|
||||
char nm[] = "worker1";
|
||||
char ky[] = "key";
|
||||
xce_peerinfo_t from = { .name = nm, .key = ky };
|
||||
|
||||
endpoint_t* ep = new_test_endpoint();
|
||||
|
||||
int rc = xcecd_resync_answer_query(list, ep, &from, jobids, 2, 40000, 0);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
xce_request_t decoded;
|
||||
pop_and_decode(ep, &decoded);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(OPCode_jobStateReport, decoded.opcode);
|
||||
TEST_ASSERT_EQUAL_INT(Request_body_jobstatereportmsg, decoded.body_which);
|
||||
TEST_ASSERT_EQUAL_INT(2, decoded.body.jobstatereportmsg->n_total);
|
||||
TEST_ASSERT_EQUAL_INT(2, decoded.body.jobstatereportmsg->count);
|
||||
|
||||
xce_free_request(&decoded);
|
||||
assert_queue_empty(ep);
|
||||
|
||||
endpoint_fini(ep);
|
||||
free(ep);
|
||||
free_worker_list(list);
|
||||
}
|
||||
|
||||
/* Requirement 8.3: nothing truncated/dropped across multiple pages, for
|
||||
* a mix of queried answers and extras. */
|
||||
static void test_answer_query_multipage_covers_every_job(void)
|
||||
{
|
||||
xcecd_worker_t* list = NULL;
|
||||
int i;
|
||||
|
||||
/* 3 queried running jobs. */
|
||||
uint64_t jobids[3] = { 6001, 6002, 6003 };
|
||||
for (i = 0; i < 3; i++) {
|
||||
link_worker(&list, make_worker(100 + i, jobids[i], WORKER_RUNNING));
|
||||
}
|
||||
/* 2 extra running jobs, not queried. */
|
||||
link_worker(&list, make_worker(200, 6004, WORKER_RUNNING));
|
||||
link_worker(&list, make_worker(201, 6005, WORKER_RUNNING));
|
||||
/* 2 extra finished jobs, not queried. */
|
||||
xcecd_retain_finished_job(6006, WORKER_DONE, 0, 90000);
|
||||
xcecd_retain_finished_job(6007, WORKER_EXITED, 3, 90500);
|
||||
|
||||
char nm[] = "worker1";
|
||||
char ky[] = "key";
|
||||
xce_peerinfo_t from = { .name = nm, .key = ky };
|
||||
|
||||
endpoint_t* ep = new_test_endpoint();
|
||||
|
||||
/* 7 total entries, pktlimit=3 -> pages of 3, 3, 1. */
|
||||
int rc = xcecd_resync_answer_query(list, ep, &from, jobids, 3, 95000, 3);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
uint64_t rebuilt[7];
|
||||
int queried_flags[7];
|
||||
int rebuilt_n = 0;
|
||||
int page;
|
||||
|
||||
for (page = 0; page < 3; page++) {
|
||||
xce_request_t decoded;
|
||||
pop_and_decode(ep, &decoded);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(OPCode_jobStateReport, decoded.opcode);
|
||||
TEST_ASSERT_EQUAL_INT(7, decoded.body.jobstatereportmsg->n_total);
|
||||
|
||||
int c = decoded.body.jobstatereportmsg->count;
|
||||
int i2;
|
||||
for (i2 = 0; i2 < c; i2++) {
|
||||
xce_job_state_entry_t* got
|
||||
= decoded.body.jobstatereportmsg->entries[i2];
|
||||
rebuilt[rebuilt_n] = got->jobid;
|
||||
queried_flags[rebuilt_n] = got->queried;
|
||||
rebuilt_n++;
|
||||
}
|
||||
|
||||
xce_free_request(&decoded);
|
||||
}
|
||||
|
||||
assert_queue_empty(ep);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(7, rebuilt_n);
|
||||
|
||||
/* Every job from 6001..6007 must appear exactly once across all
|
||||
* pages, with the correct queried flag. */
|
||||
uint64_t expected[7] = { 6001, 6002, 6003, 6004, 6005, 6006, 6007 };
|
||||
int expected_q[7] = { 1, 1, 1, 0, 0, 0, 0 };
|
||||
int j;
|
||||
for (j = 0; j < 7; j++) {
|
||||
int found = 0;
|
||||
int k;
|
||||
for (k = 0; k < 7; k++) {
|
||||
if (rebuilt[k] == expected[j]) {
|
||||
TEST_ASSERT_EQUAL_INT(expected_q[j], queried_flags[k]);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT_TRUE_MESSAGE(found, "every job must appear exactly once "
|
||||
"across all report pages, none "
|
||||
"dropped");
|
||||
}
|
||||
|
||||
endpoint_fini(ep);
|
||||
free(ep);
|
||||
free_worker_list(list);
|
||||
}
|
||||
|
||||
/* An empty query against a worker with no running/retained jobs still
|
||||
* sends exactly one (empty) report page -- not skipped. */
|
||||
static void test_answer_query_empty_still_sends_one_page(void)
|
||||
{
|
||||
uint64_t* jobids = NULL;
|
||||
char nm[] = "worker1";
|
||||
char ky[] = "key";
|
||||
xce_peerinfo_t from = { .name = nm, .key = ky };
|
||||
|
||||
endpoint_t* ep = new_test_endpoint();
|
||||
|
||||
int rc = xcecd_resync_answer_query(NULL, ep, &from, jobids, 0, 1000, 0);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
xce_request_t decoded;
|
||||
pop_and_decode(ep, &decoded);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(OPCode_jobStateReport, decoded.opcode);
|
||||
TEST_ASSERT_EQUAL_INT(0, decoded.body.jobstatereportmsg->n_total);
|
||||
TEST_ASSERT_EQUAL_INT(0, decoded.body.jobstatereportmsg->count);
|
||||
|
||||
xce_free_request(&decoded);
|
||||
assert_queue_empty(ep);
|
||||
|
||||
endpoint_fini(ep);
|
||||
free(ep);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* xcecd_finished_job_retention_snapshot: task 4.2's companion addition to
|
||||
* xcecd_worker_utils.c/h (needed to enumerate retention for extras, since
|
||||
* task 4.1's own public API was jobid-keyed lookup only).
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static void test_retention_snapshot_returns_all_unexpired_entries(void)
|
||||
{
|
||||
xcecd_retain_finished_job(9001, WORKER_DONE, 0, 10000);
|
||||
xcecd_retain_finished_job(9002, WORKER_EXITED, 1, 10500);
|
||||
|
||||
xcecd_finished_job_t* snap = NULL;
|
||||
int n = 0;
|
||||
|
||||
int rc = xcecd_finished_job_retention_snapshot(20000, &snap, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(2, n);
|
||||
TEST_ASSERT_NOT_NULL(snap);
|
||||
|
||||
free(snap);
|
||||
}
|
||||
|
||||
static void test_retention_snapshot_excludes_expired_entries(void)
|
||||
{
|
||||
xcecd_finished_job_retention_set_window_ms(1000);
|
||||
xcecd_retain_finished_job(9101, WORKER_DONE, 0, 10000);
|
||||
|
||||
xcecd_finished_job_t* snap = NULL;
|
||||
int n = 0;
|
||||
|
||||
int rc = xcecd_finished_job_retention_snapshot(10000 + 1000 + 1, &snap, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, n);
|
||||
TEST_ASSERT_NULL(snap);
|
||||
}
|
||||
|
||||
static void test_retention_snapshot_empty_store(void)
|
||||
{
|
||||
xcecd_finished_job_t* snap = (xcecd_finished_job_t*)0x1;
|
||||
int n = -1;
|
||||
|
||||
int rc = xcecd_finished_job_retention_snapshot(1000, &snap, &n);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, n);
|
||||
TEST_ASSERT_NULL(snap);
|
||||
}
|
||||
|
||||
static void test_retention_snapshot_null_out_params_rejected(void)
|
||||
{
|
||||
xcecd_finished_job_t* snap = NULL;
|
||||
int n = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
-EINVAL, xcecd_finished_job_retention_snapshot(1000, NULL, &n));
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
-EINVAL, xcecd_finished_job_retention_snapshot(1000, &snap, NULL));
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* main
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
xce_init_xcecd_log("/tmp/test_xcecd_resync.log", 1024 * 1024, 2);
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_classify_running_job);
|
||||
RUN_TEST(test_classify_finished_ok_job);
|
||||
RUN_TEST(test_classify_finished_fail_job);
|
||||
RUN_TEST(test_classify_unrecognized_job);
|
||||
RUN_TEST(test_classify_terminal_worker_entry_falls_through_to_retention);
|
||||
RUN_TEST(test_classify_worker_unknown_status_is_unrecognized);
|
||||
RUN_TEST(test_classify_expired_retention_is_unrecognized);
|
||||
|
||||
RUN_TEST(test_build_report_answers_every_queried_id_exactly_once);
|
||||
RUN_TEST(test_build_report_includes_running_extra);
|
||||
RUN_TEST(test_build_report_includes_finished_extra);
|
||||
RUN_TEST(test_build_report_queried_job_not_duplicated_as_extra);
|
||||
RUN_TEST(test_build_report_empty_query_no_state_yields_empty_result);
|
||||
RUN_TEST(test_build_report_null_out_params_rejected);
|
||||
|
||||
RUN_TEST(test_answer_query_single_page);
|
||||
RUN_TEST(test_answer_query_multipage_covers_every_job);
|
||||
RUN_TEST(test_answer_query_empty_still_sends_one_page);
|
||||
|
||||
RUN_TEST(test_retention_snapshot_returns_all_unexpired_entries);
|
||||
RUN_TEST(test_retention_snapshot_excludes_expired_entries);
|
||||
RUN_TEST(test_retention_snapshot_empty_store);
|
||||
RUN_TEST(test_retention_snapshot_null_out_params_rejected);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue