fix(protocol): atomic client-msg flag, sanitize peer messages, flush before terminal, serialization probe
This commit is contained in:
@@ -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)."""
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user