fix(protocol): atomic client-msg flag, sanitize peer messages, flush before terminal, serialization probe

This commit is contained in:
2026-09-23 23:56:31 +02:00
parent b414f197af
commit 729f3ef8e1
11 changed files with 201 additions and 44 deletions
+31
View File
@@ -2893,6 +2893,37 @@ class TestItemizeChanges:
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=["-i", "--dry-run"])
assert result.returncode == 0, f"dry-run -i failed: {result.stderr[:200]}"
def test_chunk_serialization_probes_ancestor_dir_state(self, shared_server):
"""#314: --chunk-serialization + -i must probe ancestor directory state
so a pre-existing directory with a changed mtime itemizes as an
attribute change (`.d..t......`) instead of being rendered as created
(`cd+++++++++`)."""
source = os.path.join(TEST_DATA_DIR, "itemize_chunk_serial_src")
dest = os.path.join(TEST_DATA_DIR, "itemize_chunk_serial_dst")
clean_dir(source)
clean_dir(dest)
subdir = os.path.join(source, "sub")
os.makedirs(subdir)
with open(os.path.join(subdir, "file.txt"), "wb") as fh:
fh.write(b"payload\n")
result, _ = run_client(source, dest, flags=["--preserve"], port=shared_server.port)
assert result.returncode == 0, f"seed sync failed: {result.stderr[:200]}"
# Change only the source directory's mtime; its contents stay identical
# so only the directory's time attribute differs on the rerun.
os.utime(subdir, (1_000_000_000, 1_000_000_000))
result, _ = run_client(source, dest,
flags=["--preserve", "-i", "--chunk-serialization"],
port=shared_server.port)
assert result.returncode == 0, f"chunk-serialization -i failed: {result.stderr[:200]}"
dir_lines = [line for line in result.stdout.splitlines() if line.endswith(" sub/")]
assert dir_lines == [".d..t...... sub/"], (
f"expected an attribute-change dir line, got {dir_lines!r}; "
f"full stdout={result.stdout!r}"
)
def test_changed_file_on_second_incremental_run_prints_exactly_one_line(self, shared_server):
"""A changed file itemizes exactly once on an incremental rerun while
unchanged files print nothing (no double emission)."""
+43
View File
@@ -309,6 +309,48 @@ static void test_log_set_file_null_before_fclose(void) {
EXPECT_TRUE(true);
}
/* The server's client-message channel logs a peer-controlled body: it must be
* escaped so an interior newline/CR/ANSI escape cannot forge a log line or
* move the terminal cursor. With the default stderr mode a forwarded message
* is emitted as a warning on stdout. */
static void test_log_client_message_sanitized(void) {
int pipe_fds[2];
EXPECT_EQ_INT(pipe(pipe_fds), 0);
/* Drain anything earlier tests buffered on stdout before redirecting, so the
capture holds only this test's single log line. */
fflush(stdout);
int saved_stdout = dup(STDOUT_FILENO);
EXPECT_TRUE(saved_stdout >= 0);
EXPECT_TRUE(dup2(pipe_fds[1], STDOUT_FILENO) >= 0);
close(pipe_fds[1]);
set_log_level(LOG_LEVEL_WARNING);
log_set_stderr_mode(LOG_STDERR_ERRORS);
log_client_message("forged\n2026-01-01 [ERROR]: fake\x1b[31mred");
fflush(stdout);
EXPECT_TRUE(dup2(saved_stdout, STDOUT_FILENO) >= 0);
close(saved_stdout);
char output[512] = {0};
ssize_t length = read(pipe_fds[0], output, sizeof(output) - 1);
close(pipe_fds[0]);
EXPECT_TRUE(length > 0);
/* The interior newline became an escaped octal, so the body stays on one
physical line: exactly one '\n' (the log terminator) is present. */
int newlines = 0;
for (ssize_t i = 0; i < length; i++) {
if (output[i] == '\n')
newlines++;
}
EXPECT_EQ_INT(newlines, 1);
/* The ESC introducer is escaped too, so no raw ANSI sequence reaches the
terminal. */
EXPECT_TRUE(strchr(output, '\x1b') == NULL);
EXPECT_TRUE(strstr(output, "forged") != NULL);
EXPECT_TRUE(strstr(output, "fake") != NULL);
log_set_stderr_mode(LOG_STDERR_ERRORS);
}
void test_log() {
test_log_message_debug();
test_log_message_info();
@@ -320,6 +362,7 @@ void test_log() {
test_log_filtering();
test_log_stderr_mode_all();
test_log_stderr_mode_client();
test_log_client_message_sanitized();
test_log_message_formats();
test_log_debug_enabled_matches_gate();
test_log_concurrent_no_torn_lines();