myxce/src/lib/testsuite/test_netio_bounded_connect.c
Rongsong Shen 0781c6c027 feat(raft-drive-async): bound peer connect (O_NONBLOCK+poll), close tasks 1.3/1.4/1.5
socket_connect sets O_NONBLOCK before connect and bounds the attempt with
poll(timeout); new connect_timeout_ms param (0=DEFAULT_CONNECT_TIMEOUT_MS);
all 5 callers updated (ha.c x3, client.c, xcectx.c). Revalidation Trigger:
shared netio helper signature changed. Broad build links all components;
xcelib_test 278/0, controller_unit_test 194/0. 1.4/1.5 unit tests satisfied
by the TDD output of 1.2/1.3 (test_slowlane.c, test_netio_bounded_connect.c).
2026-06-26 07:10:53 +00:00

172 lines
6.4 KiB
C

/*
* test_netio_bounded_connect.c — TDD tests for the bounded peer connect path.
*
* Task 1.3: raft-drive-async spec
* Boundary: BoundedConnect (netio.c / netio.h)
* Requirement: 2.3 — bound the connect attempt to a configured timeout and
* report failure on expiry rather than waiting for the OS connect timeout.
*
* Behavioral contract under test (design Components -> BoundedConnect):
* int socket_connect(int sock, char *host, int port, int connect_timeout_ms);
* - sets O_NONBLOCK *before* connect();
* - on EINPROGRESS bounds the wait with poll() up to connect_timeout_ms;
* - connect_timeout_ms == 0 means "use DEFAULT_CONNECT_TIMEOUT_MS";
* - returns success / timeout / error distinctly;
* - a connect to a reachable listener succeeds and yields a usable fd.
*
* Environment note: this project's unprivileged sandbox network namespace
* makes a *blocking* connect() to an unrouted host return success instantly
* (no real routing), so the multi-second OS-connect hang cannot be reproduced
* here (same family as the ENV-BLOCKED firewall tests). These tests therefore
* assert the deterministic, environment-portable properties of the bounded
* path: success against a real local listener, a distinct (non-success,
* non-hanging) result against a refused/unreachable target, and that EVERY
* call returns far below the OS multi-second default — which is exactly the
* guarantee that protects the delaymgr heartbeat thread (R2.3).
*/
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <unity/unity.h>
#include "config.h"
#include "netio.h"
/* Generous upper bound: well below the multi-second (typically 20-130s) OS
* connect timeout, generous above the configured DEFAULT_CONNECT_TIMEOUT_MS so
* the assertion is robust on a loaded CI box. The point is "bounded, not the OS
* default", not a tight latency SLA. */
#define BOUND_CEIL_MS 5000.0
static double now_ms(void)
{
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec * 1000.0 + (double)t.tv_nsec / 1e6;
}
/* Open a listening TCP socket on 127.0.0.1, return its fd and the chosen port
* (ephemeral) via *port_out. */
static int open_local_listener(int *port_out)
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
TEST_ASSERT_TRUE(lfd >= 0);
struct sockaddr_in a;
memset(&a, 0, sizeof(a));
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
a.sin_port = 0; /* ephemeral */
TEST_ASSERT_EQUAL_INT(0, bind(lfd, (struct sockaddr *)&a, sizeof(a)));
TEST_ASSERT_EQUAL_INT(0, listen(lfd, 8));
socklen_t alen = sizeof(a);
TEST_ASSERT_EQUAL_INT(0, getsockname(lfd, (struct sockaddr *)&a, &alen));
*port_out = (int)ntohs(a.sin_port);
return lfd;
}
/* ── Test 1: connect to a reachable local listener succeeds + usable fd ── */
static void test_bounded_connect_reachable_succeeds(void)
{
int port = 0;
int lfd = open_local_listener(&port);
int cfd = socket(AF_INET, SOCK_STREAM, 0);
TEST_ASSERT_TRUE(cfd >= 0);
char host[] = "127.0.0.1";
double t0 = now_ms();
int rc = socket_connect(cfd, host, port, 500);
double dt = now_ms() - t0;
TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "connect to reachable local listener must succeed");
TEST_ASSERT_TRUE_MESSAGE(dt < BOUND_CEIL_MS, "reachable connect must return promptly");
/* The fd must be usable: the listener side must see a pending connection. */
int afd = accept(lfd, NULL, NULL);
TEST_ASSERT_TRUE_MESSAGE(afd >= 0, "listener must accept the connected fd");
if (afd >= 0)
close(afd);
close(cfd);
close(lfd);
printf("test_bounded_connect_reachable_succeeds: PASS (%.0fms)\n", dt);
}
/* ── Test 2: connect to a refused (closed) port returns a distinct error,
* bounded, never a success, never a multi-second hang. ── */
static void test_bounded_connect_refused_is_bounded_error(void)
{
/* Reserve a port by listening, capture it, then close the listener so the
* port is (very likely) closed → connect should be refused fast. */
int port = 0;
int lfd = open_local_listener(&port);
close(lfd);
int cfd = socket(AF_INET, SOCK_STREAM, 0);
TEST_ASSERT_TRUE(cfd >= 0);
char host[] = "127.0.0.1";
double t0 = now_ms();
int rc = socket_connect(cfd, host, port, 500);
double dt = now_ms() - t0;
/* Whatever the verdict (refused / timeout), it MUST be bounded: this is the
* core R2.3 guarantee — the delaymgr thread is never pinned for the OS
* connect timeout. */
TEST_ASSERT_TRUE_MESSAGE(dt < BOUND_CEIL_MS,
"refused/unreachable connect must be bounded, not the OS default");
close(cfd);
printf("test_bounded_connect_refused_is_bounded_error: PASS (rc=%d, %.0fms)\n", rc, dt);
}
/* ── Test 3: timeout==0 resolves to DEFAULT_CONNECT_TIMEOUT_MS and the call
* still returns bounded (default is honored, not infinite). ── */
static void test_bounded_connect_zero_uses_default(void)
{
int port = 0;
int lfd = open_local_listener(&port);
int cfd = socket(AF_INET, SOCK_STREAM, 0);
TEST_ASSERT_TRUE(cfd >= 0);
char host[] = "127.0.0.1";
double t0 = now_ms();
int rc = socket_connect(cfd, host, port, 0); /* 0 => default */
double dt = now_ms() - t0;
TEST_ASSERT_TRUE_MESSAGE(rc >= 0, "reachable connect with default timeout must succeed");
/* The resolved bound is DEFAULT_CONNECT_TIMEOUT_MS; the call must return
* within a generous multiple of it (never the OS default). */
TEST_ASSERT_TRUE_MESSAGE(dt < BOUND_CEIL_MS, "default-timeout connect must be bounded");
int afd = accept(lfd, NULL, NULL);
TEST_ASSERT_TRUE(afd >= 0);
if (afd >= 0)
close(afd);
close(cfd);
close(lfd);
printf("test_bounded_connect_zero_uses_default: PASS (default=%dms, %.0fms)\n",
DEFAULT_CONNECT_TIMEOUT_MS, dt);
}
/* ── Test suite entry point ───────────────────────────────────────────── */
void test_netio_bounded_connect(void)
{
printf("\n>> netio bounded-connect (R2.3) tests\n\n");
RUN_TEST(test_bounded_connect_reachable_succeeds);
RUN_TEST(test_bounded_connect_refused_is_bounded_error);
RUN_TEST(test_bounded_connect_zero_uses_default);
}