Cover NTP correction, retries, faults and peer lifecycle
This commit is contained in:
1 parent
13c1831baa
commit
88bf65e07d
2 files changed
+494
No files matched your search
@@ -96,6 +96,7 @@ check_PROGRAMS = \
|
||||
|
||||
if OS_LINUX
|
||||
check_PROGRAMS += \
|
||||
test_ntp_regressions \
|
||||
test_firewall \
|
||||
test_mem \
|
||||
test_config_updater \
|
||||
@@ -573,6 +574,16 @@ test_ntp_SOURCES = test_ntp.c
|
||||
test_ntp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_ntp_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
if OS_LINUX
|
||||
test_ntp_regressions_SOURCES = test_ntp_regressions.c
|
||||
test_ntp_regressions_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
test_ntp_regressions_LDFLAGS = -Wl,--wrap=gettimeofday -Wl,--wrap=uasync_set_timeout -Wl,--wrap=uasync_cancel_timeout \
|
||||
-Wl,--wrap=socket_create_udp -Wl,--wrap=socket_set_nonblocking -Wl,--wrap=socket_close_wrapper \
|
||||
-Wl,--wrap=socket_sendto -Wl,--wrap=socket_recvfrom -Wl,--wrap=uasync_add_socket_t -Wl,--wrap=uasync_remove_socket_t \
|
||||
-Wl,--wrap=adns_resolve -Wl,--wrap=adns_cancel -Wl,--wrap=topo_node_update_my_addresses -Wl,--wrap=etcp_send \
|
||||
-Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl -Wl,--wrap=u_strdup_impl
|
||||
endif
|
||||
|
||||
test_async_dns_SOURCES = test_async_dns.c
|
||||
test_async_dns_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_async_dns_LDADD = $(COMMON_LIBS)
|
||||
|
||||
@@ -0,0 +1,483 @@
|
||||
/* Расчёт времени и отказы NTP: реальные модули/API, управляемые часы, DNS, UDP и таймеры без сети/ожиданий. */
|
||||
#define _GNU_SOURCE
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/ntp_time.h"
|
||||
#include "../src/ntp_node_time.h"
|
||||
#include "../src/transport_layer/etcp_api.h"
|
||||
#include "../src/transport_layer/etcp.h"
|
||||
#include "../lib/async_dns.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#define FAKE_SOCKET 12345678
|
||||
#define WALL_START_US 1700000000000000LL
|
||||
|
||||
struct timer {
|
||||
int active, delay_tb;
|
||||
timeout_callback_t callback;
|
||||
void* arg;
|
||||
};
|
||||
static struct timer timers[4];
|
||||
static int64_t wall_us;
|
||||
static int socket_open, socket_registered, creates, sends, peer_sends, address_updates, dns_calls, dns_cancels;
|
||||
static int fail_create, fail_nonblock, fail_send, fail_register, fail_dns;
|
||||
static const char* fail_timer;
|
||||
static int fail_alloc_at, alloc_attempts;
|
||||
static socket_t_callback_t read_callback;
|
||||
static void* read_arg;
|
||||
static adns_done_cb dns_callback;
|
||||
static void* dns_arg;
|
||||
static uint8_t request[48], reply[48];
|
||||
static int reply_length;
|
||||
|
||||
extern int __real_socket_close_wrapper(socket_t);
|
||||
extern void* __real_u_malloc_impl(uint32_t, const char*);
|
||||
extern void* __real_u_calloc_impl(uint32_t, uint32_t, const char*);
|
||||
extern char* __real_u_strdup_impl(const char*, const char*);
|
||||
|
||||
static int allocation_fault(void) { return fail_alloc_at && ++alloc_attempts == fail_alloc_at; }
|
||||
void* __wrap_u_malloc_impl(uint32_t size, const char* where) {
|
||||
return allocation_fault() ? NULL : __real_u_malloc_impl(size, where);
|
||||
}
|
||||
void* __wrap_u_calloc_impl(uint32_t count, uint32_t size, const char* where) {
|
||||
return allocation_fault() ? NULL : __real_u_calloc_impl(count, size, where);
|
||||
}
|
||||
char* __wrap_u_strdup_impl(const char* value, const char* where) {
|
||||
return allocation_fault() ? NULL : __real_u_strdup_impl(value, where);
|
||||
}
|
||||
|
||||
int __wrap_gettimeofday(struct timeval* tv, void* tz) {
|
||||
(void)tz;
|
||||
tv->tv_sec = wall_us / 1000000;
|
||||
tv->tv_usec = wall_us % 1000000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* __wrap_uasync_set_timeout(struct UASYNC* ua, int delay, void* arg, timeout_callback_t cb, const char* name) {
|
||||
assert(ua && cb && delay >= 0);
|
||||
assert(!strcmp(name, "ntp_sync") || !strcmp(name, "ntp_q"));
|
||||
if (fail_timer && !strcmp(fail_timer, name)) { fail_timer = NULL; return NULL; }
|
||||
for (size_t i = 0; i < sizeof(timers) / sizeof(timers[0]); i++) {
|
||||
if (timers[i].active) continue;
|
||||
timers[i] = (struct timer){.active = 1, .delay_tb = delay, .callback = cb, .arg = arg};
|
||||
return &timers[i];
|
||||
}
|
||||
assert(!"too many live timers");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
err_t __wrap_uasync_cancel_timeout(struct UASYNC* ua, void* handle) {
|
||||
assert(ua && handle);
|
||||
struct timer* t = handle;
|
||||
assert(t >= timers && t < timers + 4 && t->active);
|
||||
t->active = 0;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static void fire_timer(void* handle) {
|
||||
struct timer* t = handle;
|
||||
assert(t && t->active);
|
||||
struct timer saved = *t;
|
||||
t->active = 0;
|
||||
wall_us += (int64_t)saved.delay_tb * 100;
|
||||
saved.callback(saved.arg);
|
||||
}
|
||||
|
||||
static void fire_query_timer(void) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (timers[i].active && timers[i].delay_tb == 20000) { fire_timer(&timers[i]); return; }
|
||||
}
|
||||
assert(!"no query timer");
|
||||
}
|
||||
|
||||
socket_t __wrap_socket_create_udp(int family) {
|
||||
assert(family == AF_INET && !socket_open);
|
||||
creates++;
|
||||
if (fail_create) { errno = EMFILE; return SOCKET_INVALID; }
|
||||
socket_open = 1;
|
||||
return FAKE_SOCKET;
|
||||
}
|
||||
int __wrap_socket_set_nonblocking(socket_t sock) {
|
||||
assert(sock == FAKE_SOCKET && socket_open);
|
||||
errno = EIO;
|
||||
return fail_nonblock ? -1 : 0;
|
||||
}
|
||||
int __wrap_socket_close_wrapper(socket_t sock) {
|
||||
if (sock != FAKE_SOCKET) return __real_socket_close_wrapper(sock);
|
||||
assert(socket_open && !socket_registered);
|
||||
socket_open = 0;
|
||||
return 0;
|
||||
}
|
||||
ssize_t __wrap_socket_sendto(socket_t sock, const void* data, size_t len, const struct sockaddr* addr, socklen_t addr_len) {
|
||||
assert(sock == FAKE_SOCKET && socket_open && len == sizeof(request));
|
||||
assert(addr && addr_len == sizeof(struct sockaddr_in));
|
||||
sends++;
|
||||
memcpy(request, data, len);
|
||||
errno = EIO;
|
||||
return fail_send ? -1 : (ssize_t)len;
|
||||
}
|
||||
ssize_t __wrap_socket_recvfrom(socket_t sock, void* data, size_t len, struct sockaddr* addr, socklen_t* addr_len) {
|
||||
assert(sock == FAKE_SOCKET && socket_open && len >= sizeof(reply));
|
||||
(void)addr; (void)addr_len;
|
||||
memcpy(data, reply, sizeof(reply));
|
||||
return reply_length;
|
||||
}
|
||||
void* __wrap_uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t r,
|
||||
socket_t_callback_t w, socket_t_callback_t e, const char* name, void* arg) {
|
||||
assert(ua && sock == FAKE_SOCKET && socket_open && !socket_registered && r && !w && !e);
|
||||
assert(!strcmp(name, "ntp_query"));
|
||||
if (fail_register) return NULL;
|
||||
socket_registered = 1;
|
||||
read_callback = r; read_arg = arg;
|
||||
return &socket_registered;
|
||||
}
|
||||
err_t __wrap_uasync_remove_socket_t(struct UASYNC* ua, socket_t sock) {
|
||||
assert(ua && sock == FAKE_SOCKET && socket_registered);
|
||||
socket_registered = 0; read_callback = NULL; read_arg = NULL;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
struct adns_query* __wrap_adns_resolve(struct UASYNC* ua, const char* name, const struct adns_opts* opts,
|
||||
adns_done_cb cb, void* arg) {
|
||||
assert(ua && name && cb && !dns_callback);
|
||||
(void)opts;
|
||||
dns_calls++;
|
||||
if (fail_dns) return NULL;
|
||||
dns_callback = cb; dns_arg = arg;
|
||||
return (struct adns_query*)&dns_callback;
|
||||
}
|
||||
void __wrap_adns_cancel(struct adns_query* query) {
|
||||
assert(query == (struct adns_query*)&dns_callback && dns_callback);
|
||||
dns_callback = NULL; dns_arg = NULL; dns_cancels++;
|
||||
}
|
||||
static void finish_dns(int status) {
|
||||
assert(dns_callback);
|
||||
adns_done_cb cb = dns_callback;
|
||||
void* arg = dns_arg;
|
||||
dns_callback = NULL; dns_arg = NULL;
|
||||
struct adns_result result = {.status = status, .count = status == ADNS_OK ? 1 : 0};
|
||||
result.addrs[0].sin_family = AF_INET;
|
||||
result.addrs[0].sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
cb(&result, arg);
|
||||
}
|
||||
|
||||
void __wrap_topo_node_update_my_addresses(struct UTUN_INSTANCE* inst) { assert(inst); address_updates++; }
|
||||
int __wrap_etcp_send(struct ETCP_CONN* conn, struct ll_entry* e) {
|
||||
assert(conn && e && e->len == 10 && e->dgram[0] == ETCP_ID_NTP_TIME);
|
||||
peer_sends++;
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct fixture {
|
||||
struct UASYNC* ua;
|
||||
struct UTUN_INSTANCE inst;
|
||||
struct utun_config config;
|
||||
struct CFG_NTP_SERVER servers[2];
|
||||
struct ETCP_CONN conns[3];
|
||||
};
|
||||
|
||||
static void setup(struct fixture* f) {
|
||||
memset(f, 0, sizeof(*f));
|
||||
memset(timers, 0, sizeof(timers));
|
||||
wall_us = WALL_START_US;
|
||||
creates = sends = peer_sends = address_updates = dns_calls = dns_cancels = 0;
|
||||
fail_create = fail_nonblock = fail_send = fail_register = fail_dns = 0;
|
||||
fail_alloc_at = alloc_attempts = 0; fail_timer = NULL;
|
||||
f->ua = uasync_create();
|
||||
assert(f->ua);
|
||||
f->inst.ua = f->ua;
|
||||
f->inst.config = &f->config;
|
||||
f->inst.connections = queue_new(f->ua, 0, 0, 0, "ntp_test_connections");
|
||||
assert(f->inst.connections);
|
||||
f->config.global.ntp_enabled = 1;
|
||||
f->config.global.ntp_resync_interval = 3600;
|
||||
f->config.global.ntp_server_count = 2;
|
||||
f->config.global.ntp_servers = &f->servers[0];
|
||||
strcpy(f->servers[0].name, "a.invalid");
|
||||
strcpy(f->servers[1].name, "b.invalid");
|
||||
f->servers[0].next = &f->servers[1];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
f->conns[i].instance = &f->inst;
|
||||
f->conns[i].peer_node_id = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanup(struct fixture* f) {
|
||||
fail_alloc_at = 0;
|
||||
ntp_time_destroy(&f->inst);
|
||||
ntp_node_time_destroy(&f->inst);
|
||||
assert(!socket_open && !socket_registered && !dns_callback && !read_callback);
|
||||
for (int i = 0; i < 4; i++) assert(!timers[i].active);
|
||||
assert(!f->inst.new_conn_cbks && !f->inst.api_bindings.callbacks[ETCP_ID_NTP_TIME]);
|
||||
for (int i = 0; i < 3; i++) assert(!f->conns[i].cbks);
|
||||
struct ll_entry* e;
|
||||
while ((e = queue_get_data(f->inst.connections))) queue_entry_free(e);
|
||||
queue_free(f->inst.connections);
|
||||
uasync_destroy(f->ua, 0);
|
||||
}
|
||||
|
||||
static void start(struct fixture* f, int direct) {
|
||||
assert(!ntp_time_init(&f->inst));
|
||||
if (direct) ntp_time_set_test_addr(&f->inst.ntp, "127.0.0.1", 11123);
|
||||
assert(!f->inst.ntp.sync && f->inst.ntp.timer);
|
||||
fire_timer(f->inst.ntp.timer);
|
||||
}
|
||||
|
||||
static uint64_t ntp_stamp(int64_t us) {
|
||||
return ((uint64_t)(us / 1000000 + 2208988800LL) << 32) | ((uint64_t)(us % 1000000) << 32) / 1000000;
|
||||
}
|
||||
static uint64_t request_stamp(void) {
|
||||
uint64_t value;
|
||||
memcpy(&value, request + 40, sizeof(value));
|
||||
return be64toh(value);
|
||||
}
|
||||
static void deliver_reply(uint64_t origin, int64_t server_us) {
|
||||
memset(reply, 0, sizeof(reply));
|
||||
reply[0] = (4 << 3) | 4; reply[1] = 1;
|
||||
uint64_t value = htobe64(origin);
|
||||
memcpy(reply + 24, &value, sizeof(value));
|
||||
value = htobe64(ntp_stamp(server_us));
|
||||
memcpy(reply + 32, &value, sizeof(value));
|
||||
memcpy(reply + 40, &value, sizeof(value));
|
||||
reply_length = sizeof(reply);
|
||||
assert(read_callback);
|
||||
read_callback(FAKE_SOCKET, read_arg);
|
||||
}
|
||||
static void check_time(struct fixture* f, int64_t expected_us) {
|
||||
assert(ntp_time_is_synced(&f->inst));
|
||||
int64_t error = ntp_time_get_us(&f->inst) - expected_us;
|
||||
assert(error >= -2 && error <= 2);
|
||||
assert(ntp_time_get_seconds(&f->inst) == expected_us / 1000000);
|
||||
}
|
||||
|
||||
static void test_offsets(struct fixture* f) {
|
||||
for (int sign = -1; sign <= 1; sign++) {
|
||||
start(f, 1);
|
||||
deliver_reply(request_stamp(), wall_us + sign * 5000000LL);
|
||||
assert(f->inst.ntp.offset_us == -sign * 5000000LL);
|
||||
check_time(f, wall_us + sign * 5000000LL);
|
||||
assert(f->inst.ntp.reachable && !f->inst.ntp.sync && f->inst.ntp.timer);
|
||||
assert(((struct timer*)f->inst.ntp.timer)->delay_tb == 36000000);
|
||||
ntp_time_destroy(&f->inst);
|
||||
check_time(f, wall_us + sign * 5000000LL);
|
||||
}
|
||||
assert(!dns_calls);
|
||||
}
|
||||
|
||||
static void test_retry_timestamp(struct fixture* f) {
|
||||
start(f, 1);
|
||||
uint64_t first = request_stamp();
|
||||
fire_query_timer();
|
||||
uint64_t second = request_stamp();
|
||||
assert(first != second && sends == 2);
|
||||
deliver_reply(first, wall_us + 5000000);
|
||||
assert(!f->inst.ntp.synced && f->inst.ntp.sync);
|
||||
deliver_reply(second, wall_us + 5000000);
|
||||
check_time(f, wall_us + 5000000);
|
||||
}
|
||||
|
||||
static void check_failed_cycle(struct fixture* f) {
|
||||
assert(!f->inst.ntp.sync && !f->inst.ntp.reachable && f->inst.ntp.timer);
|
||||
assert(!socket_open && !socket_registered && !dns_callback && address_updates == 1);
|
||||
int active = 0;
|
||||
for (int i = 0; i < 4; i++) active += timers[i].active;
|
||||
assert(active == 1 && ((struct timer*)f->inst.ntp.timer)->delay_tb == 36000000);
|
||||
}
|
||||
static void test_async_dns_failure(struct fixture* f) {
|
||||
start(f, 0);
|
||||
finish_dns(ADNS_ERR_TIMEOUT);
|
||||
assert(dns_calls == 2 && f->inst.ntp.sync);
|
||||
finish_dns(ADNS_ERR_NODATA);
|
||||
assert(dns_calls == 2);
|
||||
check_failed_cycle(f);
|
||||
// Следующий цикл действительно запускается и может восстановить reachable.
|
||||
fire_timer(f->inst.ntp.timer);
|
||||
finish_dns(ADNS_OK);
|
||||
deliver_reply(request_stamp(), wall_us - 5000000);
|
||||
check_time(f, wall_us - 5000000);
|
||||
assert(f->inst.ntp.reachable && address_updates == 2 && f->inst.ntp.server_current == 1);
|
||||
}
|
||||
static void test_immediate_dns_failure(struct fixture* f) {
|
||||
fail_dns = 1;
|
||||
start(f, 0);
|
||||
assert(dns_calls == 2);
|
||||
check_failed_cycle(f);
|
||||
}
|
||||
static void test_udp_exhaustion(struct fixture* f) {
|
||||
start(f, 0);
|
||||
for (int server = 0; server < 2; server++) {
|
||||
finish_dns(ADNS_OK);
|
||||
for (int i = 0; i < 3; i++) fire_query_timer();
|
||||
}
|
||||
assert(dns_calls == 2 && creates == 2 && sends == 6);
|
||||
check_failed_cycle(f);
|
||||
}
|
||||
static void test_immediate_udp_failures(struct fixture* f) {
|
||||
strcpy(f->servers[0].name, "127.0.0.1");
|
||||
strcpy(f->servers[1].name, "127.0.0.2");
|
||||
int* faults[] = {&fail_create, &fail_nonblock, &fail_send, &fail_register};
|
||||
for (size_t i = 0; i < sizeof(faults) / sizeof(faults[0]); i++) {
|
||||
creates = sends = address_updates = 0;
|
||||
*faults[i] = 1;
|
||||
start(f, 0);
|
||||
assert(creates == 2 && !dns_calls);
|
||||
check_failed_cycle(f);
|
||||
ntp_time_destroy(&f->inst);
|
||||
*faults[i] = 0;
|
||||
}
|
||||
}
|
||||
static void test_timer_failures(struct fixture* f) {
|
||||
fail_timer = "ntp_sync";
|
||||
assert(ntp_time_init(&f->inst) == -1);
|
||||
assert(!f->inst.ntp.servers && !f->inst.ntp.timer && !f->inst.ntp.enabled);
|
||||
assert(!ntp_time_init(&f->inst));
|
||||
ntp_time_set_test_addr(&f->inst.ntp, "127.0.0.1", 11123);
|
||||
fail_timer = "ntp_q";
|
||||
fire_timer(f->inst.ntp.timer);
|
||||
check_failed_cycle(f);
|
||||
ntp_time_destroy(&f->inst);
|
||||
start(f, 1);
|
||||
fail_timer = "ntp_sync";
|
||||
deliver_reply(request_stamp(), wall_us + 5000000);
|
||||
check_time(f, wall_us + 5000000);
|
||||
assert(!f->inst.ntp.sync && !f->inst.ntp.timer && !socket_open);
|
||||
}
|
||||
static void test_cancel(struct fixture* f) {
|
||||
start(f, 0);
|
||||
assert(dns_callback);
|
||||
ntp_time_destroy(&f->inst);
|
||||
assert(!dns_callback && dns_cancels == 1 && !f->inst.ntp.sync);
|
||||
start(f, 1);
|
||||
assert(socket_registered);
|
||||
ntp_time_destroy(&f->inst);
|
||||
assert(!read_callback && !socket_open && !f->inst.ntp.sync);
|
||||
}
|
||||
static void test_init_failures(struct fixture* f) {
|
||||
size_t baseline = u_get_allocated_count();
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
alloc_attempts = 0; fail_alloc_at = i;
|
||||
assert(ntp_time_init(&f->inst) == -1);
|
||||
fail_alloc_at = 0;
|
||||
assert(!f->inst.ntp.servers && !f->inst.ntp.timer && u_get_allocated_count() == baseline);
|
||||
}
|
||||
f->config.global.ntp_resync_interval = INT_MAX;
|
||||
assert(ntp_time_init(&f->inst) == -1);
|
||||
f->config.global.ntp_resync_interval = 3600;
|
||||
assert(!ntp_time_init(&f->inst));
|
||||
void* timer = f->inst.ntp.timer;
|
||||
assert(ntp_time_init(&f->inst) == -1 && timer == f->inst.ntp.timer);
|
||||
alloc_attempts = 0; fail_alloc_at = 1;
|
||||
fire_timer(timer);
|
||||
fail_alloc_at = 0;
|
||||
assert(!f->inst.ntp.sync && f->inst.ntp.timer);
|
||||
}
|
||||
|
||||
static void register_connection(struct fixture* f, int index) {
|
||||
struct ll_entry* e = queue_entry_new(sizeof(struct conn_queue_entry));
|
||||
assert(e);
|
||||
((struct conn_queue_entry*)e->data)->conn = &f->conns[index];
|
||||
assert(!queue_data_put(f->inst.connections, e));
|
||||
for (struct etcp_inst_cbk_entry* cb = f->inst.new_conn_cbks; cb; cb = cb->next) cb->fn(&f->conns[index], cb->arg);
|
||||
}
|
||||
static void receive_peer(struct fixture* f, struct ETCP_CONN* conn, int synced, int64_t time_us) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
assert(e);
|
||||
e->dgram = u_malloc(10);
|
||||
assert(e->dgram);
|
||||
e->len = 10; e->dgram[0] = ETCP_ID_NTP_TIME; e->dgram[1] = synced;
|
||||
memcpy(e->dgram + 2, &time_us, sizeof(time_us));
|
||||
assert(f->inst.api_bindings.callbacks[ETCP_ID_NTP_TIME]);
|
||||
f->inst.api_bindings.callbacks[ETCP_ID_NTP_TIME](conn, e);
|
||||
}
|
||||
static void test_peer_cache(struct fixture* f) {
|
||||
assert(!ntp_node_time_init(&f->inst));
|
||||
struct ETCP_CONN* conn = &f->conns[0];
|
||||
size_t baseline = u_get_allocated_count();
|
||||
for (int i = 0; i < NTP_NODE_MAX_PEERS; i++) {
|
||||
conn->peer_node_id = i + 1;
|
||||
receive_peer(f, conn, 0, wall_us);
|
||||
f->inst.ntp_node.peers[i].last_seen_tb = i + 100;
|
||||
}
|
||||
conn->peer_node_id = 1;
|
||||
receive_peer(f, conn, 0, wall_us); // обновление сохраняет запись 1; вытеснить надо запись 2
|
||||
conn->peer_node_id = 100;
|
||||
struct ETCP_LINK link = {.rtt_last = 200}; // 20 мс, половина — 10000 мкс
|
||||
conn->links = &link;
|
||||
receive_peer(f, conn, 1, wall_us + 4990000);
|
||||
conn->links = NULL;
|
||||
check_time(f, wall_us + 5000000);
|
||||
assert(!f->inst.ntp.enabled && f->inst.ntp_node.peer_count == NTP_NODE_MAX_PEERS);
|
||||
assert(f->inst.ntp_node.peers[0].node_id == 1 && f->inst.ntp_node.peers[1].node_id == 100);
|
||||
// Ещё один новый пир получает ответ и не меняет уже принятую поправку.
|
||||
conn->peer_node_id = 101;
|
||||
receive_peer(f, conn, 0, wall_us - 5000000);
|
||||
check_time(f, wall_us + 5000000);
|
||||
assert(peer_sends == 1 && u_get_allocated_count() == baseline);
|
||||
ntp_node_time_destroy(&f->inst);
|
||||
assert(!f->inst.ntp_node.peer_count);
|
||||
check_time(f, wall_us + 5000000);
|
||||
}
|
||||
static int foreign_calls;
|
||||
static void foreign_callback(struct ETCP_CONN* conn, int event, void* arg) {
|
||||
(void)conn; (void)event; (void)arg;
|
||||
foreign_calls++;
|
||||
}
|
||||
static void test_node_lifecycle(struct fixture* f) {
|
||||
register_connection(f, 0); // pending до init
|
||||
register_connection(f, 1); // initialized до init
|
||||
f->conns[1].initialized = f->conns[1].links_up = 1;
|
||||
etcp_conn_add_cbk(&f->conns[0], foreign_callback, f, ETCP_CBK_EVENT_INIT);
|
||||
struct etcp_cbk_entry* foreign = f->conns[0].cbks;
|
||||
assert(foreign);
|
||||
for (int cycle = 0; cycle < 2; cycle++) {
|
||||
int before = peer_sends;
|
||||
assert(!ntp_node_time_init(&f->inst));
|
||||
assert(peer_sends == before + 1);
|
||||
assert(!ntp_node_time_init(&f->inst) && peer_sends == before + 1);
|
||||
if (!cycle) register_connection(f, 2); // новое pending после init
|
||||
etcp_cbk_fire(&f->conns[0], ETCP_CBK_EVENT_INIT);
|
||||
etcp_cbk_fire(&f->conns[2], ETCP_CBK_EVENT_INIT);
|
||||
assert(peer_sends == before + 3);
|
||||
ntp_node_time_destroy(&f->inst);
|
||||
ntp_node_time_destroy(&f->inst);
|
||||
assert(f->conns[0].cbks == foreign && !foreign->next);
|
||||
assert(!f->conns[1].cbks && !f->conns[2].cbks);
|
||||
etcp_cbk_fire(&f->conns[0], ETCP_CBK_EVENT_INIT);
|
||||
etcp_cbk_fire(&f->conns[2], ETCP_CBK_EVENT_INIT);
|
||||
assert(peer_sends == before + 3);
|
||||
}
|
||||
assert(foreign_calls == 4);
|
||||
etcp_conn_remove_cbk(&f->conns[0], foreign_callback, f);
|
||||
// Отказ регистрации на каждом шаге должен снять уже установленные подписки.
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
alloc_attempts = 0; fail_alloc_at = i;
|
||||
assert(ntp_node_time_init(&f->inst) == -1);
|
||||
fail_alloc_at = 0;
|
||||
assert(!f->inst.ntp_node.active && !f->inst.new_conn_cbks);
|
||||
assert(!f->inst.api_bindings.callbacks[ETCP_ID_NTP_TIME]);
|
||||
for (int j = 0; j < 3; j++) assert(!f->conns[j].cbks);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_WARN);
|
||||
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_INFO);
|
||||
void (*tests[])(struct fixture*) = {test_offsets, test_retry_timestamp, test_async_dns_failure,
|
||||
test_immediate_dns_failure, test_udp_exhaustion, test_immediate_udp_failures, test_timer_failures,
|
||||
test_cancel, test_init_failures, test_peer_cache, test_node_lifecycle};
|
||||
for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
|
||||
size_t baseline = u_get_allocated_count();
|
||||
struct fixture f;
|
||||
setup(&f);
|
||||
tests[i](&f);
|
||||
cleanup(&f);
|
||||
assert(u_get_allocated_count() == baseline);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "NTP regression %zu passed, no live allocations", i + 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user