feat(libxce-failover): 4-action status classifier with attempt-aware DUP-replay rule (task 2.1)
This commit is contained in:
parent
d11c6e1d89
commit
fa2ac05036
5 changed files with 222 additions and 1 deletions
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include "failover_harness.h"
|
||||
#include "request.h"
|
||||
#include "xce/client_failover.h"
|
||||
#include "xce/errnox.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Harness implementation
|
||||
|
|
@ -344,6 +346,110 @@ static void test_thread_harness_rejects_bad_args(void)
|
|||
printf("test_thread_harness_rejects_bad_args: PASS\n");
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Task 2.1 — StatusClassifier (xce_failover_classify) RED→GREEN tests
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Map the harness' expected-action category to the real classifier enum. The
|
||||
* two enums share order by design, but we map explicitly so a future reorder of
|
||||
* either enum is caught here rather than silently passing. */
|
||||
static xce_fo_action_t fo_expected_to_action(fo_expected_action_t e)
|
||||
{
|
||||
switch (e) {
|
||||
case FO_ACT_SUCCESS: return XCE_FO_SUCCESS;
|
||||
case FO_ACT_RETRY_NOW: return XCE_FO_RETRY_NOW;
|
||||
case FO_ACT_RETRY_BACKOFF: return XCE_FO_RETRY_BACKOFF;
|
||||
case FO_ACT_TERMINAL: return XCE_FO_TERMINAL;
|
||||
}
|
||||
return XCE_FO_TERMINAL;
|
||||
}
|
||||
|
||||
/* Req 2.5, 4.1, 4.2, 4.3, 4.5: every taxonomy row's first-send classification
|
||||
* (is_write=false, attempt=0) matches the design's StatusClassifier mapping. */
|
||||
static void test_classify_matches_taxonomy_table(void)
|
||||
{
|
||||
for (size_t i = 0; i < FO_STATUS_TABLE_LEN; i++) {
|
||||
const fo_status_row_t *row = &FO_STATUS_TABLE[i];
|
||||
xce_fo_action_t want = fo_expected_to_action(row->expect);
|
||||
xce_fo_action_t got =
|
||||
xce_failover_classify(row->status, /*is_write=*/false,
|
||||
/*attempt=*/0);
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "row '%s' (status=%d): want %d got %d",
|
||||
row->name, row->status, (int)want, (int)got);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(want, got, msg);
|
||||
}
|
||||
|
||||
printf("test_classify_matches_taxonomy_table: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 2.5: REDIRECT/STANDBY/NOMASTER/NOT_READY are never reported as success,
|
||||
* regardless of is_write/attempt. */
|
||||
static void test_classify_control_codes_never_success(void)
|
||||
{
|
||||
const int codes[] = { E_XCE_REDIRECT, E_XCE_STANDBY, E_XCE_NOMASTER,
|
||||
E_XCE_NOT_READY };
|
||||
for (size_t i = 0; i < sizeof(codes) / sizeof(codes[0]); i++) {
|
||||
TEST_ASSERT_NOT_EQUAL(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(codes[i], false, 0));
|
||||
TEST_ASSERT_NOT_EQUAL(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(codes[i], true, 1));
|
||||
/* accepted in either sign */
|
||||
TEST_ASSERT_NOT_EQUAL(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(-codes[i], true, 1));
|
||||
}
|
||||
|
||||
printf("test_classify_control_codes_never_success: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 5.2: idempotent-replay rule — DUP_JOB/EEXIST on a RE-SENT WRITE is a
|
||||
* success; the first send (or a read) is a genuine duplicate → terminal. */
|
||||
static void test_classify_dup_replay_rule(void)
|
||||
{
|
||||
/* re-sent write (is_write && attempt>0) → SUCCESS */
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(-E_XCE_DUP_JOB, true, 1));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(E_XCE_DUP_JOB, true, 1));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(-EEXIST, true, 1));
|
||||
|
||||
/* first send → genuine duplicate → TERMINAL */
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_TERMINAL,
|
||||
xce_failover_classify(-E_XCE_DUP_JOB, true, 0));
|
||||
/* non-write replay → TERMINAL */
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_TERMINAL,
|
||||
xce_failover_classify(-E_XCE_DUP_JOB, false, 1));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_TERMINAL,
|
||||
xce_failover_classify(-EEXIST, false, 1));
|
||||
|
||||
printf("test_classify_dup_replay_rule: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 3.2 / general: status 0 is always success. */
|
||||
static void test_classify_zero_is_success(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(0, false, 0));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_SUCCESS,
|
||||
xce_failover_classify(0, true, 3));
|
||||
|
||||
printf("test_classify_zero_is_success: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 4.5: unknown / unrecognized statuses are conservatively terminal. */
|
||||
static void test_classify_unknown_is_terminal(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_TERMINAL,
|
||||
xce_failover_classify(-EACCES, false, 0));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_TERMINAL,
|
||||
xce_failover_classify(-ENOENT, true, 1));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_FO_TERMINAL,
|
||||
xce_failover_classify(-12345, false, 0));
|
||||
|
||||
printf("test_classify_unknown_is_terminal: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Suite entry point ──────────────────────────────────────────────── */
|
||||
|
||||
void test_libxce_retry(void)
|
||||
|
|
@ -356,5 +462,10 @@ void test_libxce_retry(void)
|
|||
RUN_TEST(test_status_table_redirect_hint_flag);
|
||||
RUN_TEST(test_thread_harness_runs_all_threads);
|
||||
RUN_TEST(test_thread_harness_rejects_bad_args);
|
||||
RUN_TEST(test_classify_matches_taxonomy_table);
|
||||
RUN_TEST(test_classify_control_codes_never_success);
|
||||
RUN_TEST(test_classify_dup_replay_rule);
|
||||
RUN_TEST(test_classify_zero_is_success);
|
||||
RUN_TEST(test_classify_unknown_is_terminal);
|
||||
UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue