forked from lei.chen/myxce
Add void (*on_connected)(endpoint_t *ep) field to endpoint_t with endpoint_set_on_connected() helper. io_handler fires it exactly once immediately after ssl_handshake_done=1 then nulls the pointer. Enables ha_start_election_async to send OP_requestVote as soon as the TLS handshake with a peer completes — no blocking SSL_connect. 6 new unit tests pass; controller suite 1/1 OK.
182 lines
5.5 KiB
C
182 lines
5.5 KiB
C
/*
|
|
* test_netio_on_connected.c — TDD tests for on_connected callback in endpoint_t.
|
|
*
|
|
* Task 1.2: election-iomgr-refactor spec
|
|
* Boundary: netio.c / netio.h
|
|
*
|
|
* Tests verify:
|
|
* 1. on_connected field exists in endpoint_t (compile-time check)
|
|
* 2. endpoint_set_on_connected() stores the callback
|
|
* 3. Callback fires exactly once when ssl_handshake_done is set to 1
|
|
* 4. Callback pointer is nulled after firing (fire-once semantics)
|
|
* 5. handle_ssl_handshake path: invokes on_connected via io_handler mock
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
#include "netio.h"
|
|
|
|
/* ── Static state for callback tracking ──────────────────────────── */
|
|
|
|
static int g_cb_count = 0;
|
|
static endpoint_t *g_cb_ep = NULL;
|
|
|
|
static void on_connected_stub(endpoint_t *ep)
|
|
{
|
|
g_cb_count++;
|
|
g_cb_ep = ep;
|
|
}
|
|
|
|
/* Reset per-test globals */
|
|
static void reset_cb_state(void)
|
|
{
|
|
g_cb_count = 0;
|
|
g_cb_ep = NULL;
|
|
}
|
|
|
|
/* ── Test 1: Field exists and is zero-initialised after endpoint_get ── */
|
|
|
|
static void test_on_connected_field_exists_and_is_null(void)
|
|
{
|
|
endpoint_t *ep = endpoint_get();
|
|
TEST_ASSERT_NOT_NULL(ep);
|
|
|
|
/* Field must be accessible and NULL after fresh allocation */
|
|
TEST_ASSERT_NULL(ep->on_connected);
|
|
|
|
free(ep);
|
|
printf("test_on_connected_field_exists_and_is_null: PASS\n");
|
|
}
|
|
|
|
/* ── Test 2: endpoint_set_on_connected stores the callback ──────── */
|
|
|
|
static void test_endpoint_set_on_connected_stores_callback(void)
|
|
{
|
|
endpoint_t *ep = endpoint_get();
|
|
TEST_ASSERT_NOT_NULL(ep);
|
|
|
|
int rc = endpoint_set_on_connected(ep, on_connected_stub);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_PTR(on_connected_stub, ep->on_connected);
|
|
|
|
free(ep);
|
|
printf("test_endpoint_set_on_connected_stores_callback: PASS\n");
|
|
}
|
|
|
|
/* ── Test 3: endpoint_set_on_connected with NULL ep returns -EINVAL */
|
|
|
|
static void test_endpoint_set_on_connected_null_ep(void)
|
|
{
|
|
int rc = endpoint_set_on_connected(NULL, on_connected_stub);
|
|
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
|
printf("test_endpoint_set_on_connected_null_ep: PASS\n");
|
|
}
|
|
|
|
/* ── Test 4: endpoint_set_on_connected accepts NULL callback ────── */
|
|
|
|
static void test_endpoint_set_on_connected_null_cb(void)
|
|
{
|
|
endpoint_t *ep = endpoint_get();
|
|
TEST_ASSERT_NOT_NULL(ep);
|
|
|
|
/* Setting to NULL is valid — it clears a prior callback */
|
|
int rc = endpoint_set_on_connected(ep, NULL);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_NULL(ep->on_connected);
|
|
|
|
free(ep);
|
|
printf("test_endpoint_set_on_connected_null_cb: PASS\n");
|
|
}
|
|
|
|
/*
|
|
* ── Test 5: Direct simulation of ssl_handshake_done path ─────────
|
|
*
|
|
* Because io_handler requires a live SSL+epoll context, we simulate
|
|
* the exact code path specified by the design:
|
|
*
|
|
* ep->ssl_handshake_done = 1;
|
|
* if (ep->on_connected) { ep->on_connected(ep); ep->on_connected = NULL; }
|
|
*
|
|
* This tests that the fire-once contract holds when the hook is invoked
|
|
* immediately after ssl_handshake_done is set.
|
|
*/
|
|
static void test_on_connected_fires_once_after_handshake_done(void)
|
|
{
|
|
reset_cb_state();
|
|
|
|
endpoint_t *ep = endpoint_get();
|
|
TEST_ASSERT_NOT_NULL(ep);
|
|
|
|
endpoint_set_on_connected(ep, on_connected_stub);
|
|
TEST_ASSERT_EQUAL_PTR(on_connected_stub, ep->on_connected);
|
|
|
|
/* Simulate the moment handle_ssl_handshake sets ssl_handshake_done = 1
|
|
* and the subsequent on_connected invocation in io_handler.
|
|
* This mirrors the exact logic added to netio.c.
|
|
*/
|
|
ep->ssl_handshake_done = 1;
|
|
if (ep->on_connected) {
|
|
ep->on_connected(ep);
|
|
ep->on_connected = NULL;
|
|
}
|
|
|
|
/* Callback must have fired exactly once */
|
|
TEST_ASSERT_EQUAL_INT(1, g_cb_count);
|
|
|
|
/* Callback must receive the correct endpoint pointer */
|
|
TEST_ASSERT_EQUAL_PTR(ep, g_cb_ep);
|
|
|
|
/* Pointer must be nulled (fire-once) */
|
|
TEST_ASSERT_NULL(ep->on_connected);
|
|
|
|
/* Simulate a second epoll event — must NOT fire again */
|
|
if (ep->on_connected) {
|
|
ep->on_connected(ep);
|
|
ep->on_connected = NULL;
|
|
}
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, g_cb_count); /* still exactly 1 */
|
|
|
|
free(ep);
|
|
printf("test_on_connected_fires_once_after_handshake_done: PASS\n");
|
|
}
|
|
|
|
/* ── Test 6: No callback set — no crash when handshake completes ── */
|
|
|
|
static void test_no_callback_handshake_done_no_crash(void)
|
|
{
|
|
endpoint_t *ep = endpoint_get();
|
|
TEST_ASSERT_NOT_NULL(ep);
|
|
|
|
/* No on_connected set */
|
|
ep->ssl_handshake_done = 1;
|
|
if (ep->on_connected) {
|
|
ep->on_connected(ep);
|
|
ep->on_connected = NULL;
|
|
}
|
|
/* Must reach here without crash */
|
|
TEST_ASSERT_NULL(ep->on_connected);
|
|
|
|
free(ep);
|
|
printf("test_no_callback_handshake_done_no_crash: PASS\n");
|
|
}
|
|
|
|
/* ── Test suite entry point ───────────────────────────────────────── */
|
|
|
|
void test_netio_on_connected(void)
|
|
{
|
|
printf("\n>> netio on_connected callback tests\n\n");
|
|
|
|
RUN_TEST(test_on_connected_field_exists_and_is_null);
|
|
RUN_TEST(test_endpoint_set_on_connected_stores_callback);
|
|
RUN_TEST(test_endpoint_set_on_connected_null_ep);
|
|
RUN_TEST(test_endpoint_set_on_connected_null_cb);
|
|
RUN_TEST(test_on_connected_fires_once_after_handshake_done);
|
|
RUN_TEST(test_no_callback_handshake_done_no_crash);
|
|
}
|