feat(client): --port alias, --threads=N, graceful abort, keepalive
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "test_client_cli.h"
|
||||
#include "checksum.h"
|
||||
#include "client_send.h"
|
||||
#include "client_validation.h"
|
||||
#include "chmod.h"
|
||||
#include "config.h"
|
||||
@@ -579,6 +580,80 @@ static void test_parse_args_invalid_server_port() {
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* --port is a documented rsync-style alias for --server-port; both the
|
||||
* two-argument and the inline "=" spellings must work. */
|
||||
static void test_parse_args_port_alias() {
|
||||
Config* cfg = config_create();
|
||||
char* argv_space[] = {"fastsync", "--port", "9000", "/src", "/dst"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 5, argv_space, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(cfg->server_port, 9000);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
char* argv_inline[] = {"fastsync", "--port=9001", "/src", "/dst"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_inline, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(cfg->server_port, 9001);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
char* argv_long[] = {"fastsync", "--server-port=9002", "/src", "/dst"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_long, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(cfg->server_port, 9002);
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* --threads=N sizes the pipeline scanner; bare -j/--threads keeps the default
|
||||
* (scanner_threads == 0), and invalid values are rejected. */
|
||||
static void test_parse_args_threads() {
|
||||
Config* cfg = config_create();
|
||||
char* argv_eq[] = {"fastsync", "--threads=8", "/src", "/dst"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_eq, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->use_multithreading);
|
||||
EXPECT_EQ_INT(cfg->scanner_threads, 8);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
char* argv_short[] = {"fastsync", "-j", "/src", "/dst"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_short, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->use_multithreading);
|
||||
EXPECT_EQ_INT(cfg->scanner_threads, 0);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
char* argv_long[] = {"fastsync", "--threads", "/src", "/dst"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_long, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->use_multithreading);
|
||||
EXPECT_EQ_INT(cfg->scanner_threads, 0);
|
||||
config_delete(cfg);
|
||||
|
||||
const char* bad[] = {"--threads=0", "--threads=-3", "--threads=abc", "--threads=257"};
|
||||
for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) {
|
||||
cfg = config_create();
|
||||
char* argv_bad[] = {"fastsync", (char*)bad[i], "/src", "/dst"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_bad, positional_args, &positional_count), -1);
|
||||
config_delete(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/* The graceful-abort flag is a plain sig_atomic_t toggled by the handler. */
|
||||
static void test_client_abort_flag() {
|
||||
client_abort_requested = 0;
|
||||
EXPECT_FALSE(client_abort_pending());
|
||||
client_abort_requested = 1;
|
||||
EXPECT_TRUE(client_abort_pending());
|
||||
client_abort_requested = 0;
|
||||
EXPECT_FALSE(client_abort_pending());
|
||||
}
|
||||
|
||||
/* Test parse_args rejects invalid compression level (-z/--compress) */
|
||||
static void test_parse_args_invalid_compression_level() {
|
||||
Config* cfg = config_create();
|
||||
@@ -3224,6 +3299,9 @@ void test_client_cli() {
|
||||
test_parse_args_invalid_port();
|
||||
test_parse_args_non_numeric_port();
|
||||
test_parse_args_invalid_server_port();
|
||||
test_parse_args_port_alias();
|
||||
test_parse_args_threads();
|
||||
test_client_abort_flag();
|
||||
test_parse_args_invalid_compression_level();
|
||||
test_parse_args_valid_compression_level();
|
||||
test_parse_args_debug_flags();
|
||||
|
||||
@@ -460,6 +460,96 @@ static void test_send_receive_status_timed() {
|
||||
close(p[0]);
|
||||
}
|
||||
|
||||
static bool keepalive_always_abort(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A pre-buffered KEEPALIVE reply from the peer must be consumed transparently,
|
||||
leaving the first real status visible to the caller. */
|
||||
static void test_receive_status_keepalive_skips_reply() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, p[0], p[1]);
|
||||
|
||||
EXPECT_TRUE(protocol_send_status(&session, STATUS_KEEPALIVE));
|
||||
EXPECT_TRUE(protocol_send_status(&session, STATUS_OK));
|
||||
|
||||
Status received = STATUS_ERROR;
|
||||
EXPECT_TRUE(protocol_receive_status_keepalive(&session, &received, 5, 1, NULL));
|
||||
EXPECT_EQ_INT((int)received, (int)STATUS_OK);
|
||||
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
/* The abort callback ends the wait immediately, before any keepalive traffic. */
|
||||
static void test_receive_status_keepalive_aborts() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, p[0], p[1]);
|
||||
|
||||
Status received = STATUS_ERROR;
|
||||
EXPECT_FALSE(
|
||||
protocol_receive_status_keepalive(&session, &received, 5, 1, keepalive_always_abort));
|
||||
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int peer_read_fd;
|
||||
int peer_write_fd;
|
||||
bool replied;
|
||||
} KeepalivePeerArg;
|
||||
|
||||
static int keepalive_peer(void* arg) {
|
||||
KeepalivePeerArg* peer = arg;
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, peer->peer_read_fd, peer->peer_write_fd);
|
||||
Status status = STATUS_ERROR;
|
||||
if (protocol_receive_status(&session, &status) && status == STATUS_KEEPALIVE) {
|
||||
/* Model the busy receiver: it sends the real ack first, then the keepalive
|
||||
reply it owes for the queued keepalive (which the client must drain so it
|
||||
does not desynchronize the stream). */
|
||||
peer->replied = protocol_send_status(&session, STATUS_OK) &&
|
||||
protocol_send_status(&session, STATUS_KEEPALIVE);
|
||||
}
|
||||
return thrd_success;
|
||||
}
|
||||
|
||||
/* While the peer is silent the helper must emit STATUS_KEEPALIVE, then consume
|
||||
the peer's ack and drain the keepalive reply that follows it -- proving the
|
||||
inline keepalive loop works without a second writer racing the send path. */
|
||||
static void test_receive_status_keepalive_emits() {
|
||||
int to_client[2];
|
||||
int to_peer[2];
|
||||
EXPECT_EQ_INT(pipe(to_client), 0);
|
||||
EXPECT_EQ_INT(pipe(to_peer), 0);
|
||||
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, to_client[0], to_peer[1]);
|
||||
|
||||
KeepalivePeerArg peer = {.peer_read_fd = to_peer[0], .peer_write_fd = to_client[1]};
|
||||
thrd_t thread;
|
||||
EXPECT_EQ_INT(thrd_create(&thread, keepalive_peer, &peer), thrd_success);
|
||||
|
||||
Status received = STATUS_ERROR;
|
||||
EXPECT_TRUE(protocol_receive_status_keepalive(&session, &received, 10, 1, NULL));
|
||||
EXPECT_EQ_INT((int)received, (int)STATUS_OK);
|
||||
|
||||
int result = 0;
|
||||
EXPECT_EQ_INT(thrd_join(thread, &result), thrd_success);
|
||||
EXPECT_EQ_INT(result, thrd_success);
|
||||
EXPECT_TRUE(peer.replied);
|
||||
|
||||
close(to_client[0]);
|
||||
close(to_client[1]);
|
||||
close(to_peer[0]);
|
||||
close(to_peer[1]);
|
||||
}
|
||||
|
||||
void test_protocol() {
|
||||
test_send_receive_n_data();
|
||||
test_send_receive_n_data_zero();
|
||||
@@ -471,6 +561,9 @@ void test_protocol() {
|
||||
test_send_receive_status();
|
||||
test_protocol_session_io_timeout();
|
||||
test_send_receive_status_timed();
|
||||
test_receive_status_keepalive_skips_reply();
|
||||
test_receive_status_keepalive_aborts();
|
||||
test_receive_status_keepalive_emits();
|
||||
test_receive_n_data_truncated();
|
||||
test_receive_str_truncated();
|
||||
test_max_alloc_rejects_single_buffer();
|
||||
|
||||
Reference in New Issue
Block a user