feat(protocol): client-message channel and rsync partial exit 23 (2.30.0)
This commit is contained in:
@@ -36,7 +36,7 @@ from common import ( # noqa: E402
|
||||
verify_transfer,
|
||||
)
|
||||
|
||||
PROTOCOL_VERSION = b"2.29.0"
|
||||
PROTOCOL_VERSION = b"2.30.0"
|
||||
STATUS_MANIFEST = 5
|
||||
STATUS_OK = 0
|
||||
|
||||
|
||||
@@ -203,9 +203,9 @@ class TestDeviceSpecial:
|
||||
flags=["--devices"], port=port)
|
||||
finally:
|
||||
out, err = _stop_captured_server(server)
|
||||
assert result.returncode != 0, (
|
||||
f"a failed device mknod must be a transfer error like rsync (got exit 0): "
|
||||
f"{(out + err)[:300]}"
|
||||
assert result.returncode == 23, (
|
||||
f"a failed device mknod must exit 23 (rsync partial transfer), got "
|
||||
f"{result.returncode}: {(out + err)[:300]}"
|
||||
)
|
||||
received = get_dest_received_dir(DEVICE_DEST, DEVICE_SOURCE)
|
||||
assert not os.path.lexists(os.path.join(received, "chardev")), (
|
||||
@@ -215,6 +215,38 @@ class TestDeviceSpecial:
|
||||
f"receiver did not log the device creation error: out={out!r} err={err!r}"
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(os.geteuid() != 0, reason="requires root to create device nodes")
|
||||
def test_devices_nonroot_partial_removes_transferred_sources(self):
|
||||
"""rsync parity for a partial receiver run under --remove-source-files:
|
||||
the successfully transferred regular source is still removed, the
|
||||
un-creatable device source is kept, and the client exits 23 (verified
|
||||
against rsync 3.4.1: it removes ok.txt/ok2.txt, keeps the device, and
|
||||
exits 23)."""
|
||||
if os.geteuid() != 0 or shutil.which("setpriv") is None:
|
||||
pytest.skip("requires root + setpriv to run the receiver unprivileged")
|
||||
self._setup()
|
||||
os.mknod(os.path.join(DEVICE_SOURCE, "chardev"), stat.S_IFCHR | 0o666,
|
||||
os.makedev(1, 3))
|
||||
os.makedirs(DEVICE_DEST, exist_ok=True)
|
||||
os.chmod(DEVICE_DEST, 0o777)
|
||||
server, port = _start_captured_server(
|
||||
prefix=["setpriv", "--reuid=65534", "--regid=65534", "--clear-groups"])
|
||||
try:
|
||||
result, _ = run_client(DEVICE_SOURCE, DEVICE_DEST,
|
||||
flags=["--devices", "--remove-source-files"], port=port)
|
||||
finally:
|
||||
out, err = _stop_captured_server(server)
|
||||
assert result.returncode == 23, (
|
||||
f"a partial receiver run must exit 23, got {result.returncode}: "
|
||||
f"{(out + err)[:300]}"
|
||||
)
|
||||
assert not os.path.exists(os.path.join(DEVICE_SOURCE, "plain.txt")), (
|
||||
"a successfully transferred source must be removed even on a partial run"
|
||||
)
|
||||
assert os.path.exists(os.path.join(DEVICE_SOURCE, "chardev")), (
|
||||
"the source device that failed to materialize must be kept"
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(os.geteuid() != 0, reason="requires root to create device nodes")
|
||||
def test_devices_recreates_real_char_device(self, shared_server):
|
||||
"""Root-only: a source char device node is recreated on the destination
|
||||
@@ -334,6 +366,57 @@ def setup_test_data():
|
||||
shutil.rmtree(DEST_DIR, ignore_errors=True)
|
||||
|
||||
|
||||
class TestClientStderrChannel:
|
||||
"""--stderr=client: the client's own diagnostics go to the peer's stderr."""
|
||||
|
||||
@pytest.mark.ci
|
||||
def test_client_diagnostic_reaches_server_stderr(self):
|
||||
"""A client-side warning emitted during the transfer is forwarded over
|
||||
the STATUS_CLIENT_MSG channel and printed on the server's stderr, not the
|
||||
client's. A dangling symlink under -L is the deterministic trigger."""
|
||||
source = os.path.join(TEST_DATA_DIR, "client_msg_src")
|
||||
dest = os.path.join(TEST_DATA_DIR, "client_msg_dst")
|
||||
clean_dir(source)
|
||||
clean_dir(dest)
|
||||
with open(os.path.join(source, "plain.txt"), "wb") as f:
|
||||
f.write(b"payload\n")
|
||||
os.symlink("no-such-referent", os.path.join(source, "dangling"))
|
||||
server, port = _start_captured_server()
|
||||
try:
|
||||
result, _ = run_client(source, dest, flags=["-L", "--stderr=client"],
|
||||
port=port)
|
||||
finally:
|
||||
out, err = _stop_captured_server(server)
|
||||
assert "symlink has no referent" in (out + err), (
|
||||
f"client diagnostic did not reach the server stderr: out={out!r} err={err!r}"
|
||||
)
|
||||
assert "symlink has no referent" not in (result.stderr or ""), (
|
||||
f"client diagnostic must not also be written locally: {result.stderr!r}"
|
||||
)
|
||||
|
||||
@pytest.mark.ci
|
||||
def test_no_msgs2stderr_alias_uses_client_channel(self):
|
||||
"""--no-msgs2stderr is rsync's spelling of --stderr=client and now
|
||||
forwards the client's diagnostics to the server too."""
|
||||
source = os.path.join(TEST_DATA_DIR, "client_msg_alias_src")
|
||||
dest = os.path.join(TEST_DATA_DIR, "client_msg_alias_dst")
|
||||
clean_dir(source)
|
||||
clean_dir(dest)
|
||||
with open(os.path.join(source, "plain.txt"), "wb") as f:
|
||||
f.write(b"payload\n")
|
||||
os.symlink("no-such-referent", os.path.join(source, "dangling"))
|
||||
server, port = _start_captured_server()
|
||||
try:
|
||||
result, _ = run_client(source, dest, flags=["-L", "--no-msgs2stderr"],
|
||||
port=port)
|
||||
finally:
|
||||
out, err = _stop_captured_server(server)
|
||||
assert "symlink has no referent" in (out + err), (
|
||||
f"--no-msgs2stderr did not route to the server: out={out!r} err={err!r}"
|
||||
)
|
||||
assert "symlink has no referent" not in (result.stderr or "")
|
||||
|
||||
|
||||
class TestDryRun:
|
||||
def test_trust_sender_transfer_completes(self, shared_server):
|
||||
"""--trust-sender is a receiver-local policy (never sent to the peer).
|
||||
|
||||
@@ -133,14 +133,14 @@ def _seed_protocol_source(source):
|
||||
class TestProtocol:
|
||||
@pytest.mark.ci
|
||||
def test_protocol_current_version_accepted(self, shared_server):
|
||||
"""--protocol=2.29.0 (the current PROTOCOL_VERSION) is accepted and the
|
||||
"""--protocol=2.30.0 (the current PROTOCOL_VERSION) is accepted and the
|
||||
transfer completes normally."""
|
||||
source = os.path.join(TEST_DATA_DIR, "proto_ok_src")
|
||||
dest = os.path.join(TEST_DATA_DIR, "proto_ok_dst")
|
||||
shutil.rmtree(dest, ignore_errors=True)
|
||||
os.makedirs(dest)
|
||||
_seed_protocol_source(source)
|
||||
result, _ = run_client(source, dest, flags=["--protocol=2.29.0"],
|
||||
result, _ = run_client(source, dest, flags=["--protocol=2.30.0"],
|
||||
port=shared_server.port)
|
||||
assert result.returncode == 0, \
|
||||
f"--protocol current run failed: {(result.stderr or result.stdout)[:400]}"
|
||||
@@ -157,7 +157,7 @@ class TestProtocol:
|
||||
shutil.rmtree(dest, ignore_errors=True)
|
||||
os.makedirs(dest)
|
||||
_seed_protocol_source(source)
|
||||
for bad in ("2.28.0", "2.27.0", "2.26.0", "2.25.0", "2.24.0", "2.23.0", "2.22.0", "2.21.0", "2.20.0",
|
||||
for bad in ("2.29.0", "2.28.0", "2.27.0", "2.26.0", "2.25.0", "2.24.0", "2.23.0", "2.22.0", "2.21.0", "2.20.0",
|
||||
"2.19.0", "2.18.0", "2.17.0", "2.15.0", "2.16.0", "216", "31"):
|
||||
result, _ = run_client(source, dest, flags=[f"--protocol={bad}"],
|
||||
port=shared_server.port)
|
||||
|
||||
+32
-10
@@ -352,7 +352,7 @@ static void test_parse_args_protocol_accept_current() {
|
||||
Config* cfg = valid_client_config();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
char* argv_equals[] = {"fastsync", "--source-dir", "/src",
|
||||
"--dest-dir", "/dst", "--protocol=2.29.0"};
|
||||
"--dest-dir", "/dst", "--protocol=2.30.0"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 6, argv_equals, positional_args, &positional_count), 0);
|
||||
@@ -362,7 +362,7 @@ static void test_parse_args_protocol_accept_current() {
|
||||
cfg = valid_client_config();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
char* argv_space[] = {"fastsync", "--source-dir", "/src", "--dest-dir",
|
||||
"/dst", "--protocol", "2.29.0"};
|
||||
"/dst", "--protocol", "2.30.0"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 7, argv_space, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_STR(cfg->version, PROTOCOL_VERSION);
|
||||
@@ -372,10 +372,10 @@ static void test_parse_args_protocol_accept_current() {
|
||||
/* Any --protocol value other than the current PROTOCOL_VERSION must end in
|
||||
* failure (parse_args simply stores it; validate_config rejects it up front). */
|
||||
static void test_parse_args_protocol_rejects_other_versions() {
|
||||
static const char* const bad_versions[] = {"2.17", "2.16", "2.15.0", "2.16.0", "2.17.0",
|
||||
"2.18.0", "2.19.0", "2.20.0", "2.21.0", "2.22.0",
|
||||
"2.23.0", "2.24.0", "2.25.0", "2.26.0", "2.27.0",
|
||||
"2.28.0", "216", "31", "abc", ""};
|
||||
static const char* const bad_versions[] = {
|
||||
"2.17", "2.16", "2.15.0", "2.16.0", "2.17.0", "2.18.0", "2.19.0",
|
||||
"2.20.0", "2.21.0", "2.22.0", "2.23.0", "2.24.0", "2.25.0", "2.26.0",
|
||||
"2.27.0", "2.28.0", "2.29.0", "216", "31", "abc", ""};
|
||||
for (size_t i = 0; i < sizeof(bad_versions) / sizeof(bad_versions[0]); i++) {
|
||||
Config* cfg = valid_client_config();
|
||||
EXPECT_NOT_NULL(cfg);
|
||||
@@ -2323,9 +2323,9 @@ static void test_parse_args_8_bit_output() {
|
||||
}
|
||||
|
||||
static void test_parse_args_stderr_modes() {
|
||||
static const char* const modes[] = {"errors", "all", "e", "a"};
|
||||
static const LogStderrMode expected[] = {LOG_STDERR_ERRORS, LOG_STDERR_ALL, LOG_STDERR_ERRORS,
|
||||
LOG_STDERR_ALL};
|
||||
static const char* const modes[] = {"errors", "all", "client", "e", "a", "c"};
|
||||
static const LogStderrMode expected[] = {LOG_STDERR_ERRORS, LOG_STDERR_ALL, LOG_STDERR_CLIENT,
|
||||
LOG_STDERR_ERRORS, LOG_STDERR_ALL, LOG_STDERR_CLIENT};
|
||||
for (size_t i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) {
|
||||
Config* cfg = config_create();
|
||||
char option[32];
|
||||
@@ -2340,8 +2340,29 @@ static void test_parse_args_stderr_modes() {
|
||||
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||
}
|
||||
|
||||
/* rsync's deprecated --msgs2stderr / --no-msgs2stderr spellings map to
|
||||
* --stderr=all and --stderr=client respectively; the client-message channel
|
||||
* that `client` needs now exists (protocol 2.30.0). */
|
||||
static void test_parse_args_msgs2stderr_aliases() {
|
||||
Config* cfg = config_create();
|
||||
char* argv_all[] = {"fastsync", "--msgs2stderr", "/src", "/dst"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_all, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(log_get_stderr_mode(), LOG_STDERR_ALL);
|
||||
config_delete(cfg);
|
||||
|
||||
cfg = config_create();
|
||||
char* argv_client[] = {"fastsync", "--no-msgs2stderr", "/src", "/dst"};
|
||||
positional_count = 0;
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv_client, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(log_get_stderr_mode(), LOG_STDERR_CLIENT);
|
||||
config_delete(cfg);
|
||||
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||
}
|
||||
|
||||
static void test_parse_args_rejects_unsupported_stderr_modes() {
|
||||
static const char* const modes[] = {"client", "c", "invalid"};
|
||||
static const char* const modes[] = {"invalid", "x", ""};
|
||||
for (size_t i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) {
|
||||
Config* cfg = config_create();
|
||||
char option[32];
|
||||
@@ -5242,6 +5263,7 @@ void test_client_cli() {
|
||||
test_parse_args_ignore_times();
|
||||
test_parse_args_8_bit_output();
|
||||
test_parse_args_stderr_modes();
|
||||
test_parse_args_msgs2stderr_aliases();
|
||||
test_parse_args_rejects_unsupported_stderr_modes();
|
||||
test_parse_args_secluded_args();
|
||||
test_parse_args_chunk_serialization_long_form();
|
||||
|
||||
+8
-5
@@ -2924,7 +2924,7 @@ static void golden_config_populate(Config* c) {
|
||||
array_list_add(c->filters, str_dup("- /sub/dir/"));
|
||||
}
|
||||
|
||||
/* The pinned golden frame (protocol 2.29.0). The values below are the only
|
||||
/* The pinned golden frame (protocol 2.30.0). The values below are the only
|
||||
* thing that ties the generated table to the historical wire format; update
|
||||
* them ONLY with a PROTOCOL_VERSION bump and a documented reason. The 2.24.0
|
||||
* delete-plan wave changed only the version string; 2.25.0 appended the
|
||||
@@ -2936,10 +2936,13 @@ static void golden_config_populate(Config* c) {
|
||||
* (project decision), so the frame grew by one int to 886 bytes. The 2.29.0
|
||||
* symlink-xattr wave changes only the version string: the config-frame layout
|
||||
* is unchanged (use_xattrs already crosses the wire); the STATUS_SYMLINK frame
|
||||
* body grows instead. The byte-exact values are recomputed for the merged
|
||||
* layout. */
|
||||
* body grows instead. The 2.30.0 client-message/partial wave changes only the
|
||||
* version string: the config-frame layout is unchanged (the new
|
||||
* STATUS_CLIENT_MSG and STATUS_PARTIAL statuses are not part of this frame), so
|
||||
* the length stays 886 and only the hash moves. The byte-exact values are
|
||||
* recomputed for the merged layout. */
|
||||
#define GOLDEN_WIRE_LEN 886
|
||||
#define GOLDEN_WIRE_HASH 17827864270611927842ULL
|
||||
#define GOLDEN_WIRE_HASH 4169866417069573876ULL
|
||||
|
||||
static unsigned long long fnv1a_64(const unsigned char* buf, size_t len) {
|
||||
unsigned long long h = 1469598103934665603ULL;
|
||||
@@ -3021,7 +3024,7 @@ static unsigned long long capture_wire_hash(const Config* cfg, size_t* out_len)
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Byte-for-byte wire compatibility guard (protocol 2.29.0). The expected hash
|
||||
/* Byte-for-byte wire compatibility guard (protocol 2.30.0). The expected hash
|
||||
* pins the pre-X-macro byte stream; the refactor MUST NOT change it. */
|
||||
static void test_config_wire_golden() {
|
||||
if (is_running_under_valgrind())
|
||||
|
||||
@@ -117,6 +117,54 @@ static void test_log_stderr_mode_all() {
|
||||
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||
}
|
||||
|
||||
/* --stderr=client: an installed sink takes the message body and suppresses the
|
||||
* local write; a declining sink (or no sink) falls back to stderr. */
|
||||
static char g_client_msg_capture[256];
|
||||
|
||||
static bool client_msg_capture_sink(const char* message) {
|
||||
snprintf(g_client_msg_capture, sizeof(g_client_msg_capture), "%s", message);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool client_msg_decline_sink(const char* message) {
|
||||
(void)message;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void test_log_stderr_mode_client() {
|
||||
int pipe_fds[2];
|
||||
EXPECT_EQ_INT(pipe(pipe_fds), 0);
|
||||
int saved_stderr = dup(STDERR_FILENO);
|
||||
EXPECT_TRUE(saved_stderr >= 0);
|
||||
EXPECT_TRUE(dup2(pipe_fds[1], STDERR_FILENO) >= 0);
|
||||
close(pipe_fds[1]);
|
||||
|
||||
set_log_level(LOG_LEVEL_WARNING);
|
||||
log_set_stderr_mode(LOG_STDERR_CLIENT);
|
||||
|
||||
g_client_msg_capture[0] = '\0';
|
||||
log_set_client_msg_sink(client_msg_capture_sink);
|
||||
log_message(LOG_LEVEL_ERROR, "routed to peer %d", 7);
|
||||
fflush(stderr);
|
||||
EXPECT_EQ_STR(g_client_msg_capture, "routed to peer 7");
|
||||
|
||||
/* A sink that declines makes the message fall back to local stderr. */
|
||||
log_set_client_msg_sink(client_msg_decline_sink);
|
||||
log_message(LOG_LEVEL_ERROR, "fallback local");
|
||||
fflush(stderr);
|
||||
|
||||
log_set_client_msg_sink(NULL);
|
||||
log_set_stderr_mode(LOG_STDERR_ERRORS);
|
||||
EXPECT_TRUE(dup2(saved_stderr, STDERR_FILENO) >= 0);
|
||||
close(saved_stderr);
|
||||
char output[256] = {0};
|
||||
ssize_t length = read(pipe_fds[0], output, sizeof(output) - 1);
|
||||
close(pipe_fds[0]);
|
||||
EXPECT_TRUE(length > 0);
|
||||
EXPECT_TRUE(strstr(output, "fallback local") != NULL);
|
||||
EXPECT_TRUE(strstr(output, "routed to peer") == NULL);
|
||||
}
|
||||
|
||||
/* Test that log_message handles various format strings */
|
||||
static void test_log_message_formats() {
|
||||
set_log_level(LOG_LEVEL_DEBUG);
|
||||
@@ -271,6 +319,7 @@ void test_log() {
|
||||
test_log_set_level_error();
|
||||
test_log_filtering();
|
||||
test_log_stderr_mode_all();
|
||||
test_log_stderr_mode_client();
|
||||
test_log_message_formats();
|
||||
test_log_debug_enabled_matches_gate();
|
||||
test_log_concurrent_no_torn_lines();
|
||||
|
||||
+43
-5
@@ -228,7 +228,7 @@ static void test_send_receive_status() {
|
||||
|
||||
/* An unknown wire status outside the enum range must be rejected as a protocol
|
||||
* error instead of being handed to the caller as an unexpected verdict. The
|
||||
* last known enumerator (STATUS_STATS) must still be accepted, proving the
|
||||
* last known enumerator (STATUS_PARTIAL) must still be accepted, proving the
|
||||
* validation does not reject legitimate statuses. */
|
||||
static void test_receive_status_rejects_unknown() {
|
||||
int p[2];
|
||||
@@ -236,7 +236,7 @@ static void test_receive_status_rejects_unknown() {
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, p[0], p[1]);
|
||||
|
||||
Status bogus = (Status)(STATUS_STATS + 1);
|
||||
Status bogus = (Status)(STATUS_PARTIAL + 1);
|
||||
EXPECT_EQ_INT((int)write(p[1], &bogus, sizeof(bogus)), (int)sizeof(bogus));
|
||||
Status received = STATUS_OK;
|
||||
EXPECT_FALSE(protocol_receive_status(&session, &received));
|
||||
@@ -245,12 +245,12 @@ static void test_receive_status_rejects_unknown() {
|
||||
EXPECT_EQ_INT((int)write(p[1], &negative, sizeof(negative)), (int)sizeof(negative));
|
||||
EXPECT_FALSE(protocol_receive_status(&session, &received));
|
||||
|
||||
Status top = STATUS_STATS;
|
||||
Status top = STATUS_PARTIAL;
|
||||
EXPECT_EQ_INT((int)write(p[1], &top, sizeof(top)), (int)sizeof(top));
|
||||
EXPECT_TRUE(protocol_receive_status(&session, &received));
|
||||
EXPECT_EQ_INT((int)received, (int)STATUS_STATS);
|
||||
EXPECT_EQ_INT((int)received, (int)STATUS_PARTIAL);
|
||||
|
||||
Status timed_bogus = (Status)(STATUS_STATS + 7);
|
||||
Status timed_bogus = (Status)(STATUS_PARTIAL + 7);
|
||||
EXPECT_EQ_INT((int)write(p[1], &timed_bogus, sizeof(timed_bogus)), (int)sizeof(timed_bogus));
|
||||
EXPECT_FALSE(protocol_receive_status_timed(&session, &received, 5));
|
||||
|
||||
@@ -258,6 +258,43 @@ static void test_receive_status_rejects_unknown() {
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
/* STATUS_CLIENT_MSG carries a bounded, length-prefixed diagnostic string
|
||||
* (protocol 2.30.0, --stderr=client). An over-long message must be sliced to
|
||||
* MAX_CLIENT_MSG_BYTES rather than sent whole. */
|
||||
static void test_send_client_message_bounded() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
io_set_fds(p[0], p[1]);
|
||||
io_set_bwlimit(0);
|
||||
|
||||
const char message[] = "client diagnostic line";
|
||||
EXPECT_TRUE(send_client_message(0, message));
|
||||
Status received = STATUS_OK;
|
||||
EXPECT_TRUE(receive_status(0, &received));
|
||||
EXPECT_EQ_INT((int)received, (int)STATUS_CLIENT_MSG);
|
||||
char* body = receive_str(0);
|
||||
EXPECT_NOT_NULL(body);
|
||||
EXPECT_EQ_STR(body, message);
|
||||
free(body);
|
||||
|
||||
size_t big_len = MAX_CLIENT_MSG_BYTES + 100;
|
||||
char* big = malloc(big_len + 1);
|
||||
EXPECT_NOT_NULL(big);
|
||||
memset(big, 'x', big_len);
|
||||
big[big_len] = '\0';
|
||||
EXPECT_TRUE(send_client_message(0, big));
|
||||
EXPECT_TRUE(receive_status(0, &received));
|
||||
EXPECT_EQ_INT((int)received, (int)STATUS_CLIENT_MSG);
|
||||
char* big_body = receive_str(0);
|
||||
EXPECT_NOT_NULL(big_body);
|
||||
EXPECT_EQ_INT((int)strlen(big_body), (int)MAX_CLIENT_MSG_BYTES);
|
||||
free(big_body);
|
||||
free(big);
|
||||
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
static void test_receive_n_data_truncated() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
@@ -1250,6 +1287,7 @@ void test_protocol() {
|
||||
test_send_receive_int();
|
||||
test_send_receive_status();
|
||||
test_receive_status_rejects_unknown();
|
||||
test_send_client_message_bounded();
|
||||
test_protocol_session_io_timeout();
|
||||
test_protocol_server_io_timeout_floor();
|
||||
test_send_receive_status_timed();
|
||||
|
||||
Reference in New Issue
Block a user