Only 21 of 86 test_*.c group functions called their own nested UNITY_BEGIN()/UNITY_END(). Each nested call reset Unity's global NumberOfTests/TestFailures counters, so controller_test.c's final `return UNITY_END();` -- the value meson test actually gates on -- only ever reflected whichever small window of tests ran since the LAST nested reset, silently discarding pass/fail evidence for everything before it (measured: 16 of 972 total tests in one run). The same nested-BEGIN calls also caused PASS/FAIL banners to show the wrong source file for tests in files that don't reset it themselves (Unity.TestFile just carries over from whichever group last called UNITY_BEGIN()). Fix: replace every nested UNITY_BEGIN() with UnitySetTestFile(__FILE__) (sets file attribution only, no counter reset) and drop the matching UNITY_END(); add the same UnitySetTestFile(__FILE__) as the first statement to the other group functions that had neither. The 2 genuinely standalone binaries (test_job_array_sched_push.c, test_job_array_apiserver_push.c -- each has its own int main(), not registered via controller_test.c's TEST() macro) are untouched. Verified: single consolidated summary now reads "972 Tests 0 Failures 0 Ignored OK", exactly matching the ground-truth RUN_TEST call-site count across all 83 registered group files (976 total minus 4 belonging to the 2 standalone binaries). Every PASS/FAIL banner now names its own true source file. Full xce-build.sh all regression pass clean (Ok: 9, Fail: 0). No production code touched -- test-harness plumbing only.
323 lines
11 KiB
C
323 lines
11 KiB
C
/* test_apply_seam.c — raft-apply-decoupling task 2.1
|
|
*
|
|
* Proves the guarded state-transition cores extracted from mark_job_start/
|
|
* finish/fail/update (job.c) are directly callable WITHOUT going through the
|
|
* worker-report receipt path (on_job_start/on_job_finish/on_job_fail) and
|
|
* reproduce the exact same guard behavior mark_job_* had before extraction:
|
|
* - reject a backward transition (terminal -> non-terminal), state
|
|
* preserved (Req 5.2 guard, START/UPDATE)
|
|
* - no-op a duplicate terminal transition, state preserved (Req 5.1 guard,
|
|
* FINISH/UPDATE)
|
|
* - apply a forward transition normally, including a forward merge onto an
|
|
* already-terminal job (postdone-style OR, and the FAIL PERR-merge,
|
|
* neither of which the guard may over-reject)
|
|
*
|
|
* Fixture note: apply_job_*_transition() take only a job_t* plus the fact's
|
|
* payload struct — never a cluster_t* — because task 2.1 decouples hook-
|
|
* firing (the only thing that needed cluster_t->hooksys) out of these cores
|
|
* entirely. So the fixture here needs just a bare jobmgr + job_t, none of
|
|
* test_commit_barrier_reply.c's full cluster_t/persist/repl harness.
|
|
*/
|
|
|
|
#include <rtti.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "cluster.h"
|
|
#include "jobj.h"
|
|
#include "xce/types.h"
|
|
#include "xstring.h"
|
|
|
|
/* jobj.c's jobsub/startinfo tables and the process-global xstring table are
|
|
* shared across every test file in the controller_unit_test binary — guard
|
|
* idempotently so this file runs standalone or alongside the others. */
|
|
static int g_tas_jobobj_ready;
|
|
|
|
static var g_tas_jobmgr;
|
|
static job_submit_data_t g_tas_submit;
|
|
|
|
/* Bare jobmgr fixture: no cluster_t, no hooksys, no persist — the extracted
|
|
* apply_job_*_transition() cores need none of that. */
|
|
static void tas_setup(void)
|
|
{
|
|
kvmgr_params_t int_params = { .key_type = KVMGR_KEY_INT,
|
|
.nslots = 16,
|
|
.delta = 16 };
|
|
|
|
if (makeString((char*)"tas-probe") == NULL)
|
|
xstring_table_init(64);
|
|
|
|
if (!g_tas_jobobj_ready) {
|
|
TEST_ASSERT_EQUAL_INT(0, job_obj_init(16, 16));
|
|
g_tas_jobobj_ready = 1;
|
|
}
|
|
|
|
g_tas_jobmgr = makeInstance(&xkvmgr_type, &int_params);
|
|
TEST_ASSERT_NOT_NULL(g_tas_jobmgr);
|
|
}
|
|
|
|
static void tas_teardown(void)
|
|
{
|
|
if (g_tas_jobmgr != NULL) {
|
|
dereference(g_tas_jobmgr);
|
|
g_tas_jobmgr = NULL;
|
|
}
|
|
}
|
|
|
|
/* Creates a job directly in the bare jobmgr at the given initial state,
|
|
* bypassing handle_jobsubmit/create_job/on_job_* entirely. */
|
|
static job_t* tas_make_job(uint64_t jobid, uint32_t state)
|
|
{
|
|
xce_job_t jdef;
|
|
|
|
memset(&g_tas_submit, 0, sizeof(g_tas_submit));
|
|
g_tas_submit.queue = (char*)"q";
|
|
g_tas_submit.user = (char*)"tester";
|
|
|
|
memset(&jdef, 0, sizeof(jdef));
|
|
jdef.jobid = jobid;
|
|
jdef.state = state;
|
|
jdef.submit = &g_tas_submit;
|
|
|
|
return makeJob(g_tas_jobmgr, &jdef);
|
|
}
|
|
|
|
/* ── apply_job_start_transition ──────────────────────────────────────── */
|
|
|
|
static void test_apply_start_transition_applies_forward(void)
|
|
{
|
|
static unsigned int pids[2] = { 111, 112 };
|
|
job_pidinfo_t info;
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(1, JOB_STAT_PEND);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.jobid = 1;
|
|
info.npids = 2;
|
|
info.pids = pids;
|
|
|
|
/* Act directly on the job_t — no cluster_t, no on_job_start, no jobmgr
|
|
* lookup/lock dance. */
|
|
rc = apply_job_start_transition(j, &info);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_APPLIED, rc,
|
|
"a forward START on a non-terminal job must apply");
|
|
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_RUN, j->job.state);
|
|
TEST_ASSERT_NOT_NULL(j->job.pidinfo);
|
|
TEST_ASSERT_EQUAL_INT(2, j->job.pidinfo->npids);
|
|
|
|
tas_teardown();
|
|
printf("test_apply_start_transition_applies_forward: PASS\n");
|
|
}
|
|
|
|
static void test_apply_start_transition_rejects_backward_on_terminal(void)
|
|
{
|
|
job_pidinfo_t info;
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(2, JOB_STAT_DONE);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
info.jobid = 2;
|
|
|
|
rc = apply_job_start_transition(j, &info);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_REJECTED_BACKWARD, rc,
|
|
"a START on an already-terminal job must be rejected as backward "
|
|
"(Req 5.2)");
|
|
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_DONE, j->job.state,
|
|
"a rejected backward START must preserve the terminal state");
|
|
TEST_ASSERT_NULL_MESSAGE(j->job.pidinfo,
|
|
"a rejected backward START must perform no mutation at all");
|
|
|
|
tas_teardown();
|
|
printf("test_apply_start_transition_rejects_backward_on_terminal: PASS\n");
|
|
}
|
|
|
|
/* ── apply_job_finish_transition ─────────────────────────────────────── */
|
|
|
|
static void test_apply_finish_transition_applies_forward(void)
|
|
{
|
|
xce_jobfinish_t fin;
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(3, JOB_STAT_RUN);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
j->job.start_time = time(NULL) - 5;
|
|
|
|
memset(&fin, 0, sizeof(fin));
|
|
fin.jobid = 3;
|
|
fin.exit_code = 0;
|
|
|
|
rc = apply_job_finish_transition(j, &fin);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_APPLIED, rc,
|
|
"a forward FINISH on a running job must apply");
|
|
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_DONE, j->job.state);
|
|
TEST_ASSERT_NOT_NULL(j->job.exit_info);
|
|
TEST_ASSERT_EQUAL_INT(0, j->job.exit_info->exitcode);
|
|
|
|
tas_teardown();
|
|
printf("test_apply_finish_transition_applies_forward: PASS\n");
|
|
}
|
|
|
|
static void test_apply_finish_transition_noop_on_terminal_duplicate(void)
|
|
{
|
|
xce_jobfinish_t fin;
|
|
job_t* j;
|
|
time_t finish_time_before;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(4, JOB_STAT_DONE);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
j->job.finish_time = 12345; /* sentinel — must survive the no-op */
|
|
finish_time_before = j->job.finish_time;
|
|
|
|
memset(&fin, 0, sizeof(fin));
|
|
fin.jobid = 4;
|
|
fin.exit_code = 0;
|
|
|
|
rc = apply_job_finish_transition(j, &fin);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_NOOP_DUPLICATE, rc,
|
|
"a duplicate FINISH on an already-terminal job must no-op (Req 5.1)");
|
|
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_DONE, j->job.state,
|
|
"a no-op duplicate FINISH must leave the job's state unchanged");
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE((int)finish_time_before,
|
|
(int)j->job.finish_time,
|
|
"a no-op duplicate FINISH must perform no mutation at all");
|
|
|
|
tas_teardown();
|
|
printf("test_apply_finish_transition_noop_on_terminal_duplicate: PASS\n");
|
|
}
|
|
|
|
/* ── apply_job_update_transition ─────────────────────────────────────── */
|
|
|
|
static void test_apply_update_transition_rejects_backward_on_terminal(void)
|
|
{
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(5, JOB_STAT_DONE);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
|
|
/* An UPDATE that clears the terminal bit is backward (shared predicate
|
|
* with START, Req 5.2). */
|
|
rc = apply_job_update_transition(j, JOB_STAT_RUN);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_REJECTED_BACKWARD, rc,
|
|
"an UPDATE clearing a terminal bit must be rejected as backward");
|
|
TEST_ASSERT_EQUAL_UINT32_MESSAGE(JOB_STAT_DONE, j->job.state,
|
|
"a rejected backward UPDATE must preserve the terminal state");
|
|
|
|
tas_teardown();
|
|
printf("test_apply_update_transition_rejects_backward_on_terminal: PASS\n");
|
|
}
|
|
|
|
static void test_apply_update_transition_noop_on_duplicate(void)
|
|
{
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(6, JOB_STAT_RUN);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
|
|
rc = apply_job_update_transition(j, JOB_STAT_RUN); /* state == cur */
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_NOOP_DUPLICATE, rc,
|
|
"an UPDATE requesting the already-current state must no-op "
|
|
"(Req 5.1)");
|
|
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_RUN, j->job.state);
|
|
|
|
tas_teardown();
|
|
printf("test_apply_update_transition_noop_on_duplicate: PASS\n");
|
|
}
|
|
|
|
static void test_apply_update_transition_forward_merge_on_terminal_applies(
|
|
void)
|
|
{
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(7, JOB_STAT_DONE);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
|
|
/* A forward merge that keeps the terminal bit (postdone OR'ing PDONE)
|
|
* must NOT be over-rejected by the backward guard. */
|
|
rc = apply_job_update_transition(j, JOB_STAT_DONE | JOB_STAT_PDONE);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_APPLIED, rc,
|
|
"a forward merge preserving the terminal bit must apply, not be "
|
|
"rejected as backward");
|
|
TEST_ASSERT_EQUAL_UINT32(JOB_STAT_DONE | JOB_STAT_PDONE, j->job.state);
|
|
|
|
tas_teardown();
|
|
printf("test_apply_update_transition_forward_merge_on_terminal_applies: "
|
|
"PASS\n");
|
|
}
|
|
|
|
/* ── apply_job_fail_transition (no monotonic-rank guard, pin) ────────── */
|
|
|
|
static void test_apply_fail_transition_merges_perr_on_terminal(void)
|
|
{
|
|
job_failinfo_t failinfo;
|
|
job_t* j;
|
|
job_transition_result_t rc;
|
|
|
|
tas_setup();
|
|
j = tas_make_job(8, JOB_STAT_DONE);
|
|
TEST_ASSERT_NOT_NULL(j);
|
|
|
|
memset(&failinfo, 0, sizeof(failinfo));
|
|
failinfo.jobid = 8;
|
|
failinfo.stage = JOB_EXEC_STAGE_POST_EXEC;
|
|
failinfo.code = 1;
|
|
failinfo.detail = (char*)"post-exec fail after finish";
|
|
|
|
rc = apply_job_fail_transition(j, &failinfo);
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(JOB_TRANSITION_APPLIED, rc,
|
|
"mark_job_fail has no monotonic-rank guard — a FAIL always applies");
|
|
TEST_ASSERT_TRUE_MESSAGE((j->job.state & JOB_STAT_DONE) != 0,
|
|
"fail-after-finish must keep the DONE terminal bit (PERR-merge, "
|
|
"unchanged by this extraction)");
|
|
TEST_ASSERT_TRUE_MESSAGE((j->job.state & JOB_STAT_PERR) != 0,
|
|
"fail-after-finish must OR JOB_STAT_PERR onto the existing terminal "
|
|
"state");
|
|
|
|
tas_teardown();
|
|
printf("test_apply_fail_transition_merges_perr_on_terminal: PASS\n");
|
|
}
|
|
|
|
/* ── Test group entry point ──────────────────────────────────────────── */
|
|
|
|
void test_apply_seam(void)
|
|
{
|
|
UnitySetTestFile(__FILE__);
|
|
RUN_TEST(test_apply_start_transition_applies_forward);
|
|
RUN_TEST(test_apply_start_transition_rejects_backward_on_terminal);
|
|
RUN_TEST(test_apply_finish_transition_applies_forward);
|
|
RUN_TEST(test_apply_finish_transition_noop_on_terminal_duplicate);
|
|
RUN_TEST(test_apply_update_transition_rejects_backward_on_terminal);
|
|
RUN_TEST(test_apply_update_transition_noop_on_duplicate);
|
|
RUN_TEST(test_apply_update_transition_forward_merge_on_terminal_applies);
|
|
RUN_TEST(test_apply_fail_transition_merges_perr_on_terminal);
|
|
}
|